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.

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.

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.

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)

(unwind-protect

(progn (setq transaction-stream stream)

(call-next-method))

(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:

(run-frame-top-level

(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.