Chapter 3. An example

This chapter contains the following sections:

3.1 A simple example with two communicating controls
3.2 Building an application
3.3 The Doodler Tutorial
    3.3.1 What the doodler application does
    3.3.2 Developing an application
    3.3.3 The steps in developing the doodler application
    3.3.4 The doodler application windows
    3.3.5 Popping up a common dialog

This is chapter 3 of the User Guide for the Allegro CL 6.0 Integrated Development Environment (IDE). The IDE is only supported on Windows machines.

The chapters of the IDE User Guide are:

Chapter 1: Introduction to the IDE
Chapter 2: The Allegro CL Development Environment (IDE)
Chapter 3: An example (this chapter)
Chapter 4: Projects
Chapter 5: Components
Chapter 6: Designing a user interface
Chapter 7: Menus
Chapter 8: Events

The problem with examples is that to be interesting, they have to do something interesting (to you, the reader) and anything that does something interesting needs some programming. The Allegro CL IDE is a tool for designing and implementing user interfaces to applications, along with a collection of tools which make the task of programming easier, but it does not write the underlying program (the part that actually does the work, often called the engine).

We will discuss two examples in this chapter. First is a simple example where we make a form with two controls. We modify the event handler of one control so clicking on it prompts the user for a string and then displays the string in the other control. We show all steps and illustrate various windows and dialogs as we go.

Second, we discuss the application created by the Interface Builder Tutorial. We do not discuss the steps of the Tutorial in detail. Rather we discuss aspects of the finished application. If you see a feature of interest to your needs, you know that information on implementing it can be found in the Tutorial.

3.1 A simple example with two communicating controls

In this example, we create a dialog with a button and single-item-list. We will arrange it so that clicking on the button displays a dialog asking the user to enter some text that is then added to the items in the single-item-list.

Here is approximately what the system looks like on startup (all the relevant windows are shown, but somewhat rearranged):

startup.bmp (1620486 bytes)

For our simple example, first, create a subfolder of the Allegro folder to save files associated with our example. That allows them to be cleaned up easily. It is actually not necessary to save files for the first example, but if you do, it is best to place them in their own folder. We assume you create a tmp subfolder of the Allegro folder.

Second, we need to define a function that will be used in the application. This function will display the dialog asking for a string. Go to the editor workbook window (click View | Editor if the editor workbook is not visible -- that command brings it to the front). This window uses a tab control to display editor buffers, which may or may not be associated with files. It starts with a single unsaved buffer named Untitled:

Add the following to Untitled:

(in-package :cg-user)
(defun ask-for-string (prompt &optional (string ""))
  (multiple-value-bind (string1 string2 result)
      (ask-user-for-string prompt string 
                           "~OK" "~Cancel")
    (declare (ignore string2))
    (when (string-equal result "~OK")
      string1)))

While typing this in, try some editor tricks:

Bring up the Apropos dialog by clicking Search | Apropos. Enter ask-user-for in the String box and click Search. All symbols whose names start with ask-user-for are displayed; ask-user-for-string is at the bottom.

apro-3-2..bmp (696398 bytes)

Select it by clicking on it (it is selected in the illustration). Edit | Copy copies it to the clipboard. Back in the Untitled buffer, Edit | Paste pastes it into the buffer. Pressing F1 causes the help page for ask-user-for-string to be displayed.

Search | Complete Symbol displays a menu of choices (a dialog if there are a lot of choices). When you get to multiple-value-bind, only type multiple-v and click Search | Complete Symbol. This pop-up menu is displayed:

Now press `a'. The `a' choice, multiple-value-bind, is printed in the editor.

Notice too that the Lisp code indents as you go to new lines. When you are done, the buffer should look like this:

ew-3-3.bmp (766694 bytes)

Place the cursor just after the final parenthesis (or anywhere within the definition) and click Tools | Incremental Evaluation. You should see ask-for-string printed in the Debug window. That indicates that the function definition has been evaluated in the Lisp environment, as we want.

Note that there are shortcut keys for most of the menu choices we have mentioned (typically Control-C for Edit | Copy, Control-V for Paste, Control-. for Symbol Completion, and Control-E for Incremental Evaluation). However, your editor mode may use those key combinations for editing operations making them unavailable as menu shortcuts. The shortcuts are shown in the menu. Use them in preference to the menu if desired and if they are not used by the editor.

If you are saving files, save Untitled to tmp/excode.cl now.

Now we can design our application window. We will work with the initial project and form. Display the Project Manager window by clicking View | Project Manager.

pm-g-3-4.bmp (570078 bytes)

Now select the blank form. If it is not visible, click on (and so select) form1 (not saved) in the Project Manager and click on the View Selected Form button (shown in the illustration).

The form should appear. The Inspector should be inspecting Form1. If it is not (or is not visible), double-click on the interior of the blank form. The Inspector, inspecting form1, will appear.

Also now display the code associated with form1 in an editor buffer. Do this by selecting form1 in the Project Manager dialog and clicking on the View Selected Code button.

This will display an unsaved form1 buffer in the Editor Workbook.

ew--f1-3-6.bmp (766694 bytes)

We will be adding code for event handlers for controls on the form in this buffer presently. (Note: if we do not display this buffer before we start editing event handling code, the initial event handling function code is stored in a hidden buffer. Then when this form1 buffer is used, redefinition warnings will be displayed when the code is compiled. Such warnings, if they appear, may be safely ignored.)

Now back to the inspection of Form1. (Double-click on the form if the inspector is hidden). The inspector looks like this:

Change the title of the form to Simple Example by selecting the original value (Form1) as follows. Select the text (Form1) which is the value of the title property and type Simple Example. Alternatively, click on the extended editor button (with the three dots, on the right, when the title property is selected, and replace Form1 with Simple Example in the dialog that appears and click OK. The title of form1 will change to Simple Example. (This change is already made in the illustration.)

pw-but-sil-3-8.bmp (279354 bytes)

The form will now look like this:

f1-3-9.bmp (740054 bytes)

We want to change the name and the range of the single-item-list. Select the single-item-list by clicking on it. Sizing handles (the solid squares) appear around the selected component, as in the illustration. The Inspector window should now be inspecting the single-item-list component. Click in the name property, and type :list-box. Then click in the range field and type nil. Here is the Inspector after both changes have been made. Notice the single-item-list on the form is now blank (not illustrated). That is because we have set the range (the things displayed) to nil. In the application, when a user clicks the button, a dialog asking for a string will appear and whatever the user enters will become an item in the single-item-list.

Now we want to change the name and the title of the button. Select the button, and go to the Inspector, change the name field to :add-button and the title field to Add…. Here is the Inspector when those changes are made. Also notice the title of the button on the form is changed to Add… .

We want to specify the behavior when a user clicks on the button. When the user clicks, the on-change event handler for the button (if there is one) is called. We need a handler function that asks the user for a string and makes the result an item on the single-item-list.

To modify the on-change event handler for the button, select the button and look at the Inspector. Click on the Events button and you will see the event handlers for the button. Actually, there aren't any – all the values are nil meaning all events are ignored (the on-change-test, the only item whose value is not nil, is an exception we do not discuss here).

ew--f1-3-13b.bmp (766694 bytes)

The cursor should be in the blank line in the middle of the function definition. Enter the following line. The modified editor should look like the illustration.

 (ask-for-string "Enter string to add to list" "Greetings")

ew--f1-3-13.bmp (766694 bytes)

After you have saved the files or not, an active dialog appears. When you click on the Add button, a dialog asking for a string, with default Greetings, should appear. We show part of the form and the two dialogs.

proj-3-14.bmp (855350 bytes)

Add the following code, replacing all the current code below the declare line. The illustration shows the appearance after these lines are typed in.

(let ((list-box (find-sibling :list-box widget))
      (string (ask-for-string 
               "Enter string to add to list"
               "Greetings")))
  (when string 
    (setf (range list-box) 
      (adjoin string (range list-box)
              :test 
              #'string-equal))
    ))
 t)

ew--f1-3-15.bmp (766694 bytes)

Note that list-box was found using the function find-sibling and the name, :list-box. There are a number of find- functions. find-sibling is best in this case because only the other controls on the form have to be examined. This function now changes the range of list-box to include the entered string (adjoin adds an item to a list if it is not already there – if the new string is equal to a string already in the range, it is not added).

We now have a dialog with the controls communicating with each other. And we were able to modify the behavior while the form was running.

3.2 Building an application

We can convert this simple example into a standalone application. Early in the example, we mentioned the Project manager window and used it (if needed) to display the blank form. Since then we haven't said much about projects.

A project is a collection of modules associated with an application. With the modules and Allegro CL itself, you can build your application. Modules are parts of the application and have one or more associated files. Form1 is a module and has associated files form1.cl and form1.bil (we discuss these types of files in more detail in chapter 4).

We have to be sure that the project contains all the modules necessary, form1 and the untitled (or excode.cl if you saved it) files. Lots of files are included in an application so we strongly recommend that you start with an empty folder (like the tmp subfolder we recommended early in this discussion).

The steps are:

  1. Include all necessary files in the project. The project manager should look like this, with just form1 (unsaved):
  2. Add tmp/excode.cl (save the Untitled buffer to that file if you haven't already done so). Add the file by clicking on the + button on the Project Manager toolbar and specifying the file to the dialog that appears. Alternatively, select this buffer in the Editor Workbook, right click on the editor pane, and select Add File to Project from the shortcut menu that appears. (This avoids having to browse for the file.) After the addition, the Project manager looks like this:
  3. Save the files. (A project will not build unless all files are saved.) Click Files | Save All. This will save all unsaved files associated with the project. Save form1.cl to tmp/form1.cl and project1.lpr (the project file) to tmp/project1.lpr.
  4. Build the application exe. Click File | Build Project exe.

A Creating Image window appears and information about what is happening is printed. Note that you cannot do anything while the image is being created other than waiting.

Look in the tmp folder. You should see my-project.exe (assuming you accept my-project as the project name) and a bunch of other files. There will likely be no DLL files. Needed DLL's are not copied to the directory. If missing DLL's prevent startup of my-project.exe, copy the DLL's in the Allegro directory and try startup again.

Starting my-project.exe by double-clicking on it runs the application. End the application by clicking on the Close button of the dialog.

3.3 The Doodler Tutorial

The Doodler tutorial is another application whose complete source is included with the IDE. The tutorial illustrates features of the Allegro CL IDE. We recommend that you run the tutorial separately. Here we discuss features of the final product rather than taking you through it step by step.

The code, examples, and so on can be found in the tutorial folder and its subfolders. We strongly recommend that you use the tutorial to familiarize yourself with the product. It exercises many of the features of the system, often showing more than one way to achieve a particular end. It provides many programming examples (already written). Our hope is that parts of the tutorial will give you examples that will be useful to you in your own program design. And the result is a program that is at least visually interesting even if it is not directly relevant to whatever application you want to write.

3.3.1 What the doodler application does

The tutorial application is called the doodler (the name of the project is the :interface-builder-tutorial project). It has four windows and dialogs: one for choosing a background color,

one for defining a cycloidal curve (by specifying the three relevant coefficients),

one for listing and managing the curves defined,

and the main window upon which the specified curve or curves are drawn.

The files in the final subfolder of the tutorial folder show all the files associated with the interface-builder-tutorial application after it is completed. Except for the files util.cl, cycloid.cl, colorx.cl, all of which implement the application – drawing the curve and choosing a color -- and the several bmp files, providing the illustrations for the buttons, all the files needed for the application are generated using the Allegro CL IDE. (Some files, such as the Help file doodler.hlp, provide assistance in using the tutorial but are not needed by the doodler application.)

Here are the elements of the project listed by the doodler project manager.

We shall return to the doodler application as this chapter progresses. Now we discuss developing applications in general.

3.3.2 Developing an application

Broadly, the steps to developing an application are these:

  1. Decide what the application should do. All applications start with initial data and initial input, accept additional input from the user while running, and display results based on the initial and runtime data. The results may be output continuously, or from time to time, or only at the end. We use `output' in a wide sense, including visual display on the screen, sound from speakers, control signals or messages to external devices run by the program, and printing of data to some sort of file. You must decide, broadly, what data your application will work on and what results will be displayed and how these results will be calculated.
  2. Write or procure the application engine. In the last line of step one, we say you must determine `how these results will be calculated'. We call the part of the program which calculates the results the `engine'. It is possible the engine is already available -- your task is to provide the user interface. Or maybe you have to write it yourself. The engine in interface-builder-tutorial draws the curve in the doodler window. A compiler is an engine, as is a searching program, a drawing program, an editor, and so on. Engines are usually rather simple, in that they take inputs and produce outputs. It is the job of the user interface to procure these inputs and make the outputs available in a useful form.
  3. Decide on what to do with the results. This step and the next (Decide on how to collect the input) can be done in either order, but providing inputs programmatically (for testing) is often easier and an application that cannot communicate results is useless. In this step, you decide on the format in which the output of the engine will be provided -- displayed as a picture or text, as sound from a speaker, as a data file or data output appropriate as input to another application, or as commands sent directly to a device. We will mostly focus on display of results. Writing to a file is usually fairly straightforward once you know what to write. The output for the interface-builder-tutorial application is the pretty picture, for example. It could also (or in addition) be a copy of the picture sent to your printer, a graphics file written to disk, or, if interface-builder-tutorial was a component of a larger application, a pixmap object passed to the larger application.
  4. Decide on how to collect the input. There are many ways to collect input: from a file, from another program, from the user directly. Reading from a file is usually not complicated. You just have to find the file and understand its format. Reading from another program is often harder (because interprocess communication is often complicated) but conceptually, at least, it is straightforward. Interactive input from the user will be the main focus of our discussion, both in this chapter and in this manual as a whole.
  5. Just as there are many ways to say something, there are many ways to collect input from a user. The doodler mostly uses buttons, but some numeric input uses an editable text control with an associated up-down control:

    It also uses a color choice control that is a standard Windows dialog for choosing colors. (Note: that dialog is not associated with one of the forms of the interface-builder-tutorial project. It is displayed by ask-user-for-color.) Many are designed for and can be used for collecting user input. User input from the mouse is still input.

  6. Implement 3 and 4. Much of the work of implementing collection of input (4) and display of output (3) can be done using the various tools supplied with the Allegro CL IDE, as we describe in this manual.
  7. Fiddle. One of the most powerful features of Allegro CL is that modifications are relatively easy and often can be done with a change in only one module while all others can be left alone.

3.3.3 The steps in developing the doodler application

  1. Decide what interface-builder-tutorial should do. Now, of course the real purpose of interface-builder-tutorial is to show off the features of the application-building tools available in Allegro CL, but that real purpose confuses our purpose. The apparent purpose (which we discuss here) is to draw pictures (of cycloid curves) in a window, allowing aesthetic enjoyment and obtaining information about how cycloid curves are affected by changes in the defining equation. Here are three curves differing only in the value of the A coefficient, 100, 200, and 300 for the smallest, middle, and largest curves.
  2. Write or procure the application engine. The engine in this application draws the specified curves. The file cycloid.cl contains the code which calculates the information necessary to draw the curves. (Curves like these are drawn by calculating the x and y coordinates of many points of the curve. The curve is drawn by connecting those dots.) Fortunately, this engine is provided and does not have to be written at this time.
  3. Decide on what to do with the results. The output is a drawing of one or more curves. This drawing could be printed or written to a bitmap file, or drawn on the screen. Doodler draws it on the screen.
  4. Decide on how to collect the input. What input might be necessary? Here is all the information used to draw the curves:
  5. The background color of the drawing pane.

    How many curves to draw.

    The A, B, and C coefficients of each curve.

    The line color of each curve.

    Additionally, the user can erase the drawing pane, center the drawing pane, and scroll the drawing pane (all information about user desires that must be input somehow).

    It is reasonable to have a window that lists all curves and allows the user to add and delete curves from that list. Another window can be used for specifying the details of each curve, and another for specifying the background color. Finally, the user must somehow give the command to draw the specified curves. This arrangement (a description of the actual interface-builder-tutorial windows) is obviously one choice among many. The draw command could be placed on the doodler window rather than on the curves window, or the erase button could be placed below the Draw All button on the curves window. The background color could be implemented by a Background Color button on the doodler window (that displayed a color choice dialog immediately) rather than having a background color window with a few choices and an option to get more choices. There are no end of possible arrangements, some being obviously unsatisfactory (dialogs for each coefficient rather than one dialog for all three) but many being equally satisfactory.

    Notice that this step is longer to describe that the others, because there are so many possibilities and so few reasons to strongly prefer one choice over another.

  6. Implement 3 and 4. The tutorial goes into great detail about implementing 3 and 4. We strongly recommend that you use the tutorial to learn the basics. In this chapter, we will discuss some details of implementing 3 and 4 as a way of describing how to do things that you will want to do in most all applications. However, we will not describe the process of creating the interface-builder-tutorial application step by step.
  7. Fiddle. In 4 above, we mention some choices which are not obviously better than other possibilities. Maybe the Draw All button should be on the doodler window itself, like the erase button, rather than on the curves window. One reason for this decision is the buttons on the doodler window have picture labels and there is no obvious icon for draw like there is for erase. (The button on the doodler window that might be draw, the one illustrating a drawn curve, actually displays the curve-dialog button.) Maybe there should not be a Background Color window at all, just a color button on the doodler window that directly displays the common color choice dialog. You can doubtless think of other possible changes, some of which would, from your point of view, improve the interface-builder-tutorial application. Note that the process of fiddling with an application typically adds features, capabilities, and choices, thereby increasing the application size and complexity and making it harder to maintain.

3.3.4 The doodler application windows

There are four windows associated with the doodler application. Each window is designed by a form during the design process, so there are four forms in the interface-builder-tutorial project.

These forms are listed on the Options tab of the interface-builder-tutorial Project Manager. The project started (as all new projects do) with one form. Three others were added after. Each form was added by clicking on New Form in the File menu. When you click on File | New Form, this dialog appears asking for the device property of the form:

There are more choices shown in the illustration then will be present in a fresh Allegro CL. The additional choices, doodler, curve-dialog, coefficient-dialog and background-palette, were all added as the project was worked on. You are choosing the type of window corresponding to the form. The default choices (everything but the four listed as new) provide many capabilities, but creating new classes of devices is common because you get to add features to the new device class without affecting any existing example of that class. Consider the doodler choice (the device of the doodler window). The doodler class is defined as follows (in final\doodler.cl):

(defclass doodler (bitmap-window)
    ((doodler-curve-dialog
           :accessor doodler-curve-dialog
           :initform nil)
        (doodler-background-palette
              :accessor doodler-background-palette
              :initform nil)))

That says doodler is a subclass of bitmap-window (a bitmap window is one with an interior bitmap-pane upon which you can draw). It has two additional slots: doodler-curve-dialog and doodler-background-palette. They are not initialized. Later, they are given as values the two dialogs (corresponding to the curve-dialog and background-palette forms). A show-curve-dialog method is defined as follows:

(defmethod show-curve-dialog ((window doodler))
     (let* ((dialog (doodler-curve-dialog window))
                      (curve-list nil))
             (when (or (not dialog)
                       (not (windowp dialog)))
                        (setf dialog 
                          (make-curve-dialog :parent window))
                        (setf (doodler-curve-dialog window) dialog)
                        (setf curve-list 
                          (find-widget :curve-list dialog))
                        (setf 
                         (range curve-list)
                         (list 
                          (make-instance 'cycloidal-curve)))
                        (move-window dialog
                                     (window-to-screen-units window
                                       (make-position
                                        (- (+ (exterior-width dialog) 10))
                                        40))))
             (select-window dialog)))

This method is specialized to instances of the doodler class (and thus cannot affect any other window in the system). Without analyzing the code too closely, if the value of the doodler-curve-dialog slot is non-nil, it is assumed to be a curve-dialog and displayed; if it is nil, a curve-dialog is created and displayed. The argument to this method is the current window (a doodler window).

Why are things done this way? The design is to have a button on the doodler window which, when clicked, displays the associated curve-dialog. This is implemented by having the system call show-curve-dialog with the parent (doodler) window as its argument when the button is clicked. That action displays (creating if necessary) a curve-dialog associated with the doodler window. The dialog knows which doodler window it is associated with (because that doodler window is its parent) and the doodler window knows which curve-dialog it has (because it is the value of the doodler-curve-dialog slot of the doodler window).

3.3.5 Popping up a common dialog

Windows has provided many standard (called common) dialogs for common actions. One is the file choice dialog (a dialog that lets you pick a file on the system) that everyone is familiar with. Another is the color choice dialog. The background-color dialog allows a user to choose a background color for the doodler window. Several choices are provided along with a way to get many more choices and even make a custom color using a common color-choice dialog (clicking the Other Color button).

Let us look at how this is implemented. The code is in background-palette.cl (the code associated with the background-palette form) and colorx.cl, an auxiliary source file not specifically associated with a form. In background-palette.cl, we find the function called when the Add Colors button is clicked:

(defun background-palette-color-button-on-change 
          (widget new-value old-value)
    (declare (ignore-if-unused widget 
                               new-value old-value))
    (when new-value 
           (add-other-color (parent widget)))
    (not new-value))

add-other-color, defined in colorx.cl, is defined as follows:

(defmethod add-other-color ((dialog color-mixin))
     (let* ((new-color (ask-user-for-color
                        :initial-color (current-color dialog)))
                      (color-list (find-widget 
                                   :color-list dialog))
                      (color-name nil))
            ;; do nothing if user canceled
       (when new-color
            ;; do not add color if it already is on the list.
  #| So far, a color-choose common dialog has been displayed and the user 
     has either specified a new color or canceled. If a new color is chosen, 
     it is added to the list of defined background colors and the multi-picture 
     button is expanded to include the new color.
   |#
           (when (not (setf color-name 
                        (find-color-name dialog new-color)))
                   (setf color-name (new-color-name dialog))
                   (let* ((colors (range color-list)))
                             (setf (range color-list)
                               (append colors
                                       (list (make-instance 'button-info
                                               :name color-name
                                               :image new-color
                                               :string nil
                                               :height nil
                                               :tooltip nil
                                               :help-string nil))))))
            ;; change which color is pressed
            (setf (value color-list) (list color-name)))))

Go to chapter 4. Go to beginning of this chapter.

Copyright (c) 1998-2000 by Franz Inc. All rights reserved.