Previous: An application displaying a data structure, Up: The First Application


3.7 Incremental redisplay

While the example in the previous section is a very simple way of structuring an application (let commands arbitrarily modify the data structure, and simply erase the pane and redisplay the structure after each iteration of the command loop), the visual result is not so great when many objects are to be displayed. There is most often a noticeable flicker between the moment when the pane is cleared and the objects are drawn. Sometimes this is inevitable (as when nearly all objects change), but most of the time, only an incremental modification has been made, and most of the objects are still in the same place as before.

In simple toolkits, the application programmer would have to figure out what has changed since the previous display, and only display the differences. CLIM offers a mechanism called incremental redisplay that automates a large part of this task. As we mentioned earlier, CLIM captures output in the form of output records. The same mechanism is used to obtain incremental redisplay.

To use incremental redisplay, Client code remains structured in the simple way that was mention above: after each iteration of the command loop, the display function output the entire data structure as usual, except that it helps the incremental redisplay mechanism by telling CLIM which piece of output corresponds to which piece of output during the previous iteration of the command loop. It does this by giving some kind of unique identity to some piece of output, and some means of indicating whether the contents of this output is the same as it was last time. With this information, the CLIM incremental redisplay mechanism can figure out whether some output is new, has disappeared, or has been moved, compared to the previous iteration of the command loop. As with re-exposure, CLIM guarantees that the result is identical to that which would have been obtained, had all the output records been output in order to a blank pane.

The next example illustrates this idea. It is a simple application that displays a fixed number (here 20) of lines, each line being a number. Here is the code:

     (in-package :common-lisp-user)
     
     (defpackage "APP"
       (:use :clim :clim-lisp)
       (:export "APP-MAIN"))
     
     (in-package :app)
     
     (define-application-frame superapp ()
       ((numbers :initform (loop repeat 20 collect (list (random 100000000)))
     	    :accessor numbers)
        (cursor :initform 0 :accessor cursor))
       (:pointer-documentation t)
       (:panes
         (app :application
     	 :height 400 :width 600
     	 :incremental-redisplay t
     	 :display-function 'display-app)
         (int :interactor :height 200 :width 600))
       (:layouts
         (default (vertically () app int))))
     
     (defun display-app (frame pane)
       (loop for element in (numbers frame)
     	for line from 0
     	do (princ (if (= (cursor frame) line) "*" " ") pane)
     	do (updating-output (pane :unique-id element
     				  :id-test #'eq
     				  :cache-value (car element)
     				  :cache-test #'eql)
     	     (format pane "~a~%" (car element)))))
     
     (defun app-main ()
       (run-frame-top-level (make-application-frame 'superapp)))
     
     (define-superapp-command (com-quit :name t) ()
       (frame-exit *application-frame*))
     
     (define-superapp-command (com-add :name t) ((number 'integer))
       (incf (car (elt (numbers *application-frame*)
     		  (cursor *application-frame*)))
     	number))
     
     (define-superapp-command (com-next :name t) ()
       (incf (cursor *application-frame*))
       (when (= (cursor *application-frame*)
     	   (length (numbers *application-frame*)))
         (setf (cursor *application-frame*) 0)))
     
     (define-superapp-command (com-prev :name t) ()
       (decf (cursor *application-frame*))
       (when (minusp (cursor *application-frame*))
         (setf (cursor *application-frame*)
     	  (1- (length (numbers *application-frame*))))))
     
     
     

We store the numbers in a slot called numbers of the application frame. However, we store each number in its own list. This is a simple way to provide a unique identity for each number. We could not use the number itself, because two numbers could be the same and the identities would not be unique. Instead, we use the cons cell that store the number as the unique identity. By using :id-test #'eq we inform CLIM that it can figure out whether an output record is the same as one that was issued previous time by using the function eq to compare them. But there is a second test that has to be verified, namely whether an output record that was issued last time has to be redisplayed or not. That is the purpose of the cache-value. Here we use the number itself as the cache value and eql as the test to determine whether the output is going to be the same as last time.

For convenience, we display a * at the beginning of the current line, and we provide two commands next and previous to navigate between the lines.

Notice that in the declaration of the pane in the application frame, we have given the option :incremental-redisplay t. This informs CLIM not to clear the pane after each command-loop iteration, but to keep the output records around and compare them to the new ones that are produced during the new iteration.