COMP 14, Fall 2000

Prasun Dewan[1]

15. Toolkits

User-interface toolkits, typically referred to simply as toolkits, allow us to go beyond console-based user interfaces by supporting the implementation of what are sometimes called WIMP (Window, Icon, Menu, and Pointer) user-interfaces, also referred to a graphical user interfaces. Programs that use toolkits are very different from those that use console input/output libraries, using several new concepts such as asynchronous input, layout management, painting a window, and hierarchical visual structures. Fortunately, we have already seen some of these concepts in our study of MVC. A large variety of toolkits have been developed; they all share these concepts, differing mainly in details such as the look and feel of a button or a text field. We will look at two of them, AWT and Swing, both of which have been developed for Java. AWT is faster, and a more mature and universal toolkit, available on all Java platforms – in particular handheld computers; while Swing is easier to program.

Console-based Vs Graphical User-Interface

As mentioned above, Toolkits were developed to support graphical user interfaces. To understand the features they provide, we must understand some of the basic differences between these interfaces and console-based user interfaces:

  • Graphics Vs Text: A graphical user-interface can draw graphical figures on the screen such as points, circles, and squares whereas a console window can only show text. Thus a toolkit provides several calls to draw figures on the screen.
  • Hierarchical Vs Linear Structure: A graphical window can have a hierarchical structure - in the point history example, the plotter and bar chart windows can be decomposed into a view and control panel, and the control panel itself would be decomposed into the text fields and buttons. On the other hand, a console window always consists of a linear sequence of lines. A unit of the hierarchical structure of a window is called (by the particularly uninformative name)h a widget. Toolkits allow us to dynamically link widgets in arbitrary hierarchical structures.
  • Editing Vs Transcript-based Interaction: A graphical user-interface allows us to enter a new input value by editing a previous input or output value. Conversely, it can show new output by simply replacing the previous output or input value, whereas a console window does not allow an output line to be changed. In a console window, once a line has been entered by the user or displayed by the program, it cannot be edited by the user or replaced with a new output. For instance, in the graphical control panels we created for a point history, we can enter a new point by editing the previous point, whereas in the console window we must renter the coordinates completely on each new line. Similarly, in the graphical views we created for point history, we could display the new history by redrawing the display window, whereas in the console window, we could not replace the previous output with a new one.

Because they display the entire history of interaction, showing all input commands and output values, console-based user interfaces are also called transcript user interfaces. They are also called teletype user-interfaces, because they are modeled after a type writer. In contrast graphical user interfaces are called editing interfaces because they allow modification of previous input and output values.

To support editing user interfaces, a toolkit provides editable widgets such as text fields and checkmarks that can be changed both by users (to provide new input) and programs (to display new output).

  • User-driven Interaction: A console user-interface explicitly requests input from the user, blocking the program until the next input is given. This kind of interaction is called program-driven interaction, because the program decides when input values are received and in what order they are received. A graphical user-interface, on the other hand, provides user-driven interaction. In this interaction, the user is in control of determining when input values are entered and in what order they are provided. In the console window for a point history, we must enter the y coordinate after the x coordinate, while in the control panels, a user can edit the x field first and then the y field, or vice versa. In user-driven interaction, there is no explicit call made by the program to solicit the next input. Instead, it is always ready to process user-actions, and the toolkit notifies it when new input is available much in the manner a model notifies a view when its displayed state changes.
  • Point & Click: A graphical window allows users to enter commands by pointing and clicking, while a console window requires the entry of text. Toolkits, therefore, provide several components such as buttons and menus that we can point and click on, and notify programs when these events occur.

Because of these differences between console –based and graphical user interfaces, a toolkit provides a very different programming interface from the one provided by the console- or stream-based classes such as DataInputStream we saw earlier. We will understand this interface by seeing how it can be used to implement the two plotter and barchart editors of the point history.

Widget Structure

Let us consider, first, the plotter editors. The hierarchical visual structure of the plotter editors, shown in Figure 1.

Figure 135 Visual Structure of Plotter Frame

To understand this structure, we need to distinguish between different kinds of widgets. A top-level window is called a frame widget. A frame is a special kind of (screen) container widget, that is, a widget that contains other screen widgets, called component widgets. Examples of component widgets include text fields, buttons, and checkboxes. Examples of container widgets include menus and panels. A component widget can itself be a container widget, thereby allowing us to create a hierarchy of widgets. For instance, the control panel widget above is contained in a frame widget and contains text fields and buttons. Similarly, menus can contain submenus.

To specify a particular widget structure, we must create a corresponding object structure. Each widget in the former is represented in the latter by an an object, which we shall call a widget object. A widget object defines the behavior of the corresponding widget, displaying it on the screen and allowing the user and program to update it. A widget object is (possibly indirectly, through subtyping) an instance of some predefined type called a widget type. In particular, each component-widget object is an instance of ( some subclass of) java.awt.Component, and each container-widget object is an instance of (some subclass of ) java.awt.Container. Container is a sublcass of Component, because, as mentioned above, all container-widgets are also component-widgets. If a component-widget is contained in a container-widget, then the widget object of the former is a component of the widget object for the latter. Container widget-objects provide a method called add() to dyamically add new components to them.

Because there is a one-to-one correspondence in Java toolkits between widgets (screen areas) and the widget-objects (Java objects) that manage them, we will often not distinguish between them, using the same term to refer to both. In particular, we will use the term component (container) to refer to both a component-widget(container-widget) and the coresponding component-widget object (container-widget object).

Besides Container and Component, several other widget types are provided to implement different kinds of widgets, as we see below.

Frames

AWT cand Swing toolkits provides the classes java.awt.Frame and javax.swing.JFrame for defining frames. The following method in the composer shows how a frame is created and manipulated:

public static void createAWTPlotterEditor (PointHistoryModel pointHistoryModel) {

Frame frame = new Frame("Points Plotter");

createAWTPlotterEditor(frame, pointHistoryModel);

// important to set location, default is(0,0)

setLocation (frame);

frame.setSize (FRAME_WIDTH, FRAME_HEIGHT);

// important to make frame visible, default is invisible

frame.setVisible(true);

}

The name of a frame appears in its title and is given as an argument to the constructor used to instantiate it. Once the frame has been constructed, its components must be defined. This is the task of the call to the two-argument overloaded createAWTPlotterEditor() method:

createAWTPlotterEditor(frame, pointHistory);

After components have been added to a frame, we must set the location and size of the frame, and make it visible. Creating a frame object does not immediately result in the creation of the physical window, since we might wish to arrange several components in it before we show it to the user. The physical window is created when the setVisible() method is invoked on the frame.

We do not want the frames of the different user-interfaces to be located at the same screen position. Therefore, we will call the setLocation() method in the main class to make sure they are offset from each other:

static int curX = 0;

static int curY = 0;

static final int X_OFFSET = 100;

static final int Y_OFFSET = 100;

static void setLocation (Frame frame) {

frame.setLocation (curX, curY);

curX += X_OFFSET;

curY += Y_OFFSET;

}

Warning: if you do not see a frame, make sure that the program sets its size and makes it visible, after adding components to the frame. Also make sure the frame is not being covered by another frame of the same size and at the same location.

Border Layout Manager

The visual structure shown in Figure 1 applies to both the AWT and Swing plotter editor even though the control panel is placed at different locations within the frame. This is because the visual structure describes containment but not placement details. The placement of a component within a container is done by an object called a layout manager. Java provides several kinds of layout managers, and associates each container with a default layout manager.

The default layout manager for a frame is a java.awt.BorderLayout. It places components into five areas: NORTH, SOUTH, EAST, WEST, and CENTER. These areas do not have to be filled with components; the center component expands to fill any gap left by an unfilled area. When adding a component to a container, we can specify where it should be placed.

The two-argument implementation of the overloaded method, createAWTPlotterEditor() illustrates the use of BorderLayout:

public static void createAWTPlotterEditor(Container container, PointHistoryModel pointHistoryModel) {

container.add (createAWTController(pointHistoryModel), BorderLayout.WEST);

// fill the remaining area of the frame with the view

container.add (createPlotter(pointHistoryModel), BorderLayout.CENTER);

}

This method calls

createAWTController(pointHistory)

to create the control panel. The control panel is itself a container whose components consist of the two text fields and the two buttons. Similarly, it calls

createPlotter(pointHistory)

to create the view panel, which is the widget that plots the model. It puts the control panel in the west and the view panel in the center by specifying the position as the second argument to the add() method used to add a panel to the frame.

It is possible to invoke an argument-less version of add:

container.add (createPlotter(pointHistory);

which is equivalent to:

container.add (createPlotter(pointHistory), BorderLayout.CENTER);

If two components are added in the same area, only one of them is shown.

Warning: If you do not see both the control and view panels, make sure you have not added them to the same area by calling the argument-less version of add().

BorderLayout is only one the many layout managers provided by the toolkit. Another important layout manager, is GridLayout, which arranges the components in a matrix. This is exactly the kind of layout we want for the components of the control panel, which are arranged in a single-column matrix. The number of rows and columns in the grid created by an instance of GridLayout can be specified in its constructor. For instance, in our example, we can execute:

new GridLayout(numComponents,1)

to create the layout manager for the control panel. Since this a single-column grid, the second argument specifies one column. The first component specifies the number of rows in this column.

Whenever we change the number of components in a container whose layout is managed by a GridLayout instance, it is important to ensure that we inform the layout manager about the new number of rows and columns. For example, in the case of the control panel, we must make sure that numComponents stores the number of components of the control panel. If the grid we request cannot fit all the components of the controller, the layout manager creates additional columns. The layout manager positions components on the grid based on the order in which they were added to the container.

If we do not want the default layout manager for a container, we can execute the setLayout () method on it to bind it to a new layout manager. For example, we can bind the GridLayout instance above to the control panel by invoking:

controlPanel.setLayout(new GridLayout(NUM_COMPONENTS)

In general, a layout manager defines high-level paramaterized constraints for placing components in a container. Its users need only specify the values of these parameters such as the number of columns. The layout manager is responsible for doing the exact placement required to meet the contraints. If we wish to do the exact placement of a component in a container, speciying the coordinates of its position, we can use an instance of FormLayoutManager.

At this point in the development of the plotter editor, the widget structure is:

As mentioned before, this widget structure also definesto an object structure – the structure of widget-objects created to implement the widget structure.

MVC in a Toolkit Application

How does this object structure derived from the widget structure of the plotter window relate to the MVC architecture we plan to use to create the plotter editors?

The view-panel is responsible for drawing a point history. But this is also the job of a view in the MVC framework. Thus, these two can be the same object.

Similarly, the control-panel is responsible for providing input commands for manipulating the model. This is also the job of a controller in the MVC framework. Thus, these two can also be the same object.

The control and view panels, thus, are instances of the controller and view classes, APointHistoryAWTController and APointHistoryPlotter, respectively.

Toolkit-based View

As indicated by the discussion above, the plotter view must perform two tasks:

  • Panel: It must behave as a panel that can be put inside a frame and perform associated duties such as resizing itself as the size of the frame changes.
  • Model listener: It must listen to notifications from the model and react by redrawing itself.

Fortunately for us, its class does not have to implement the first role. The toolkit defines a class, Panel, that implements these details . Our view class can simply inherit this implementation by extending this class and add functionality to implement the second role. Thus, the view class is:

an extension of the class Panel so that it can perform the first role, and an implementation of the interface PointHistoryListener so that it can perform the first role:

public class APointHistoryPlotter extends Panel implements PointHistoryListener {

This is the first class we have seen that has two super types:

APointHistoryPlotter IS-A PointHistoryListener

APointHistoryPlotter IS-A Panel

much as, in the real world, a person can be both a teacher and a student, playing multiple roles.

Paint Method & Graphics

As a point history listener, the view must update itself in response to a change in the model. The way for a component to paint in the screen area it occupies is to override the paint()method defined by Component.

public void paint(Graphics g) {

drawPlot(g);

}

This method is automatically called by Java whenever the display of the component must be refreshed. It can also be called explicitly by invoking the repaint() method inherited from Component. Thus, whenever it is notified that the model is changed, the plotter view calls the repaint method

public void pointHistoryUpdated() {

repaint();

}

which, in turn, triggers a call to the paint method.

Why not call the paint method directly? Notice that paint method takes a Graphics argument. This is an object created by the Java toolkit that can draw figures in a component. When we call the repaint method, the toolkit locates this object and passes it to the paint method, which can then ask the object to draw various kinds of figures. Each graphics object is bound to a particular component, and can draw only in the area occupied by it.

Drawing Operations

We can invoke several methods on a graphics object to draw shapes on the screen. The operation:

drawLine( int x1, int y1, int x2, int y2 )

draws a line, between the points (x1, y1) and (x2, y2). The operation:

drawString( String s, int x, int y )

prints the string, from left to right, starting at point (x,y). The operation:

drawRect( int x, int y, int width, int height )

draws a rectangle of the specified width and height whose upper left corner is the point (x,y). Finally, to draw an ellipse or a circle, we can use:

drawOval( int x, int y, int width, int height )

As in the case of drawRect(), the coordinates describe a rectangle on the screen. The operation inscribes an ellipse in the rectangle.The graphics object also provides operations to draw geometric shapes filled with its color. For instance:

fillOval( int x, int y, int width, int height)

is like drawOval, except that it fills the interior of the oval. The default color of a graphics object is black. We can invoke the operation:

setColor(Color newColor)

to dynamically change the color of the graphics object.