9.3 Initializing CLIM Application Frames

There are several ways to initialize an application frame:

The value of an application frame's slot can be initialized using the :initform slot option in the slot's specifier in the define-application-frame form. This technique is suitable if the slot's initial value does not depend on the values of other slots, calculations based on the values of initialization arguments, or other information that cannot be determined until after the application frame is created. See the macro clos:defclass to learn about slot-specifiers.

  1. For initializations that depend on information which may not be available until the application frame has been created, an :after method can be defined for clos:initialize-instance on the application frame's class. Note that the special variable *application-frame* is not bound to the application, since the application is not yet running. The macro with-frame-state-variables cannot be used in this context, either. You may use clos:with-slots , clos:with-accessors , or any slot readers or accessors that have been defined.
  2. A :before method for run-frame-top-level on the application's frame is probably the most versatile place to perform application frame initialization. This method will not be executed until the application starts running. *application-frame* will be bound to the application frame, and you can use with-frame-state-variables in this context.
  3. If the application frame employs its own top-level function, then this function can perform initialization tasks at the beginning of its body. This top-level function may call default-frame-top-level to achieve the standard behavior for application frames.

Of course, these are only suggestions. There might be other techniques which might be more appropriate for your application. Of those listed, the :before method on run-frame-top-level is probably the best for most circumstances.

Although application frames are CLOS classes, do not use clos:make-instance to create them. To instantiate an application frame, always use make-application-frame . This function provides important initialization arguments specific to application frames that clos:make-instance does not. make-application-frame passes any keyword value pairs which it does not handle itself on to clos:make-instance , so it will respect any initialization options which you give it, just as clos:make-instance would.

Here is an example of how an application frame's behavior might be modified by inheritance from a superclass. Suppose we wanted our application to record all the commands that were performed while it was executing, because the program is for the financial industry, where it is important to keep audit trails for all transactions. As this is a useful functionality that might be added to any of a number of different applications, we will make it into a special class that implements the desired behavior. This class can then be used as a superclass for any application that needs to keep a log of its actions.

The class has an initialization option, :pathname , which specifies the name of the log file. It also has a slot named transaction-stream whose value is a stream opened to the log file when the application is running.

(defclass transaction-recording-mixin ()
  ((transaction-pathname :type pathname
                         :initarg :pathname
                         :reader transaction-pathname)
   (transaction-stream :accessor transaction-stream)))

We use an :around method on run-frame-top-level , which opens a stream to the log file and stores it in the transaction-stream slot. unwind-protect is used to clear the value of the slot when the stream is closed.

(defmethod clim:run-frame-top-level :around
  ((frame transaction-recording-mixin)) 
  (with-slots (transaction-pathname transaction-stream)
              frame (with-open-file (stream transaction-pathname
                                            :direction :output)
                          (progn (setq transaction-stream stream)
                        (setq transaction-stream nil)))))

This is where the actual logging takes place. The command loop in default-frame-top-level calls execute-frame-command to execute a command. Here we add a :before method that will log the command.

(defmethod clim:execute-frame-command :before  
  ((frame transaction-recording-mixin) command)
  (format (transaction-stream frame) "~&Command: ~a" command)) 

It is now an easy matter to alter the definition of an application to add the command logging behavior. Here is the definition of the puzzle application frame from the CLIM demos suite (from the file <release-directory>/demo/puzzle.lisp ). We use the superclasses argument to specify that the puzzle application frame should inherit from transaction-recording-mixin . Because we are using the superclass argument, we must also explicitly include application-frame , which was included by default when the superclasses argument was empty.

(define-application-frame puzzle
  (transaction-recording-mixin application-frame) 
  ((puzzle :initform (make-array '(4 4))
           :accessor puzzle-puzzle)) 
  (:default-initargs :pathname "puzzle-log.text") 
  (:panes (title :title) 
          (menu :command-menu)
          (display :application
                   :default-text-style '(:fix :bold :very-large)
                   :incremental-redisplay t 
                   :display-function draw-puzzle)))

Also note the use of (:default-initargs :pathname "puzzle-log.text") to provide a default value for the log file name if the user doesn't specify one.

The user might run the application by executing the following:

 (make-application-frame 'puzzle 
                         :width 400
                         :height 500
                         :pathname "my-puzzle-log.text"))  

Here the :pathname initialization argument is used to override the default name for the log file (as was specified by the :default-initargs clause in the previously defined application frame) and to use the name my-puzzle-log.text instead.

Common Lisp Interface Manager 2.0 User's Guide - 27 Feb 2008