Next Previous Up Top Contents Index

3 Improving The Design

3.4 Defining a new frame class

To begin with, define a frame class using the layout hierarchy you have already created. Although it might seem redundant to implement an inelegant layout again, it is easier to illustrate the basic techniques using a design you are already familiar with. In addition, there are several elements in the design that can be reused successfully.

Add the code described in this section to frame.dylan.

Defining a new class of frame is just like defining any Dylan class, except that there are several extra options available beyond the slot options normally available to define class. Each of these extra options lets you describe a particular aspect of the user interface. To define the new frame class, use the following structure:

define frame <task-frame> (<simple-frame>)
  // definitions of frame slots and options go here
end frame <task-frame>;

In this case, <task-frame> is the name of the new class of frame, and <simple-frame> is its superclass. Like ordinary Dylan classes, frame classes can have any number of superclasses, with multiple superclasses separated by commas. The superclass of any "standard" frame is usually <simple-frame>. If you were designing a dialog box, its superclass would be <dialog-frame>. If you were designing a wizard, its superclass would be <wizard-frame>.

Adding slots to a frame class is exactly the same as adding slots to a standard Dylan class. You can define slot names, init-keywords, init-functions, default values, and so on. For this example, you are not defining any slots.

Each user interface element in the new class of frame is specified as a pane with a name and a definition. A pane is a sheet within a layout, and you can think of panes as sheets that represent concrete classes in an interface (as opposed to abstract classes). In effect, specifying a pane allows you to group together existing gadgets into some meaningful relationship that effectively creates a new gadget, without actually defining a gadget class.

The name is used to refer to the pane, both from within the frame definition itself, and from other code. The pane definition includes code to create the interface element. A pane specification also includes a place to declare a local variable that can be used within the pane's definition to refer to the surrounding frame.

The following code fragment defines the two buttons, the text field, the radio box, and the list box from the initial design:

pane add-button (frame)
  make(<push-button>, label: "Add task",
       activate-callback: not-yet-implemented);
pane remove-button (frame)
  make(<push-button>, label: "Remove task",
       activate-callback: not-yet-implemented);
pane task-text (frame)
  make(<text-field>, label: "Task text:",
        activate-callback: not-yet-implemented);
pane priority-box (frame)
  make (<radio-box>, label: "Priority:", 
       items: #("High", "Medium", "Low"), 
       orientation: #"vertical",
       activate-callback: not-yet-implemented);
pane task-list (frame)
  make(<list-box>, items: #(), lines: 15,
       activate-callback: not-yet-implemented);

Note that the definition of each element is identical to the definitions included in the original layout described in Section 2.3 on page 6 (except that activate callbacks have been added to the code). Adding (frame) immediately after the name of each pane lets you refer to the frame itself within the frame definition using a local variable. This means that you can refer to any pane within the frame using normal slot syntax; that is, a pane called my-pane can be referred to as frame.my-pane throughout all of the definition of the frame class. This ability is essential when you come to layout each pane in the frame itself.

In addition, you need to define the layout in which to place these panes. This is itself just another pane, and its definition is again identical to the original layout described in Section 2.3 on page 6, with one exception; rather than defining each element explicitly, you just include a reference to the relevant pane that you have already defined using normal slot syntax, thus:

pane task-layout (frame)
  horizontally ()
    frame.task-list; 
    vertically () 
      horizontally () 
        frame.task-text;
        frame.add-button;
      end;
      frame.remove-button;
      frame.priority-box;
    end;
  end;

To describe the top-level layout for the frame, you need to refer to this pane using the layout option, as follows:

layout (frame) frame.task-layout;

You actually have a certain amount of freedom when choosing what to define as a pane in the definition of a frame class. For example, the layout in the task-layout pane actually contains a number of sub-layouts. If you wanted, each one of these sub-layouts could be defined as a separate pane within the frame definition. Note, however, that you only have to "activate" the top-most layout; there should only be one use of the layout option.

Similarly, you are free to use whatever programming constructs you like when defining elements in your code. Just as in the earlier examples, you could define the layouts with explicit calls to make, rather than by using the horizontally and vertically macros. Thus, the following definition of task-layout is just as valid as the one above:

pane task-layout (frame)
  make(<row-layout>,
       children: vector(frame.task-list,
                        make(<column-layout>,
                             children: 
                               vector(make(<row-layout>,
                                           children:
                                             vector
                                              (frame.task-text,
                                               frame.add-button)
                                          )))));

Notice that this construct is rather more complicated than the one using macros!

Throughout this section, you may have noticed that you can identify a sequence of steps that need to occur inside the definition of a frame. It is good practice to keep this sequence in mind when writing your own frame-based code:

1. Define the content panes
2. Define the layout panes
3. Use the layout option

If you glue all the code defined in this section together, you end up with the following complete definition of a frame class.

define frame <task-frame> (<simple-frame>)
  pane add-button (frame)
    make(<push-button>, label: "Add task",
         activate-callback: not-yet-implemented);
  pane remove-button (frame)
    make(<push-button>, label: "Remove task",
         activate-callback: not-yet-implemented);
  pane task-text (frame)
    make(<text-field>, label: "Task text:",
         activate-callback: not-yet-implemented);
  pane priority-box (frame)
    make(<radio-box>, label: "Priority:",
         items: #("High", "Medium", "Low"),
         orientation: #"vertical",
         activate-callback: not-yet-implemented);
  pane task-list (frame)
    make (<list-box>, items: #(), lines: 15,
          activate-callback: not-yet-implemented);
  pane task-layout (frame)
    horizontally ()
      frame.task-list; 
      vertically () 
        horizontally () 
          frame.task-text;
          frame.add-button;
        end;
        frame.remove-button;
        frame.priority-box;
      end;
    end;
  layout (frame) frame.task-layout;
  keyword title: = "Task List Manager";
end frame <task-frame>;

Note the addition of a title: keyword at the end of the definition. This can be used to give any instance of the frame class a title that is displayed in the title bar of the frame's window when it is mapped to the screen.

At this stage, the application still has no real functionality, and there is no improvement in the interface compared to the initial design, but by defining a frame class, the implementation is inherently more robust, making it easier to modify and, eventually, maintain.

If you want to try running your code, remember that you need to define some additional methods to create a frame instance and exit it cleanly. Methods for doing this were provided in Section 3.2 on page 13. If you define these methods now, you can create running versions of each successive generation of the application as it is developed.


Building Applications Using DUIM - 26 May 1999

Next Previous Up Top Contents Index