JTF > Rationale > The acm.gui Package |
Of all the problems reported by the computing education community that arise from using Java at the introductory level, the most difficult one for the Task Force to solve was the lack of appropriate components for creating simple graphical user interfaces, more commonly known as GUIs. In the taxonomy presented in Chapter 3, this problem was expressed as follows:
A3. | GUI components inappropriate for beginners |
From our initial search for strategies to address this problem, we concluded that the chance of finding a solution that would appeal to any large segment of the education community was remote and that the tools already provided by Java were likely to prove more successful than any alternative we could provide. In fact, the original decision of the Task Force was to abandon the search for a solution to this problem after several failed attempts to come up with a satisfactory design. In the first draft of this Rationale document, we summarized our failure to identify a workable solution as follows:
Unfortunately, the Task Force has not been able to come up with a satisfactory design. Whats more, some members of the Task Force have become convinced that it is not possible to create a design that a significant fraction of the potential audience would accept as a standard. That conclusion may be incorrect. There may be a design out there around which a consensus might emerge. As of this release, however, the Task Force has not been able to find it.[JTF05]
In the feedback that we received in response to the draft proposal, however, we were strongly encouraged to look harder for a solution to this problem, which was clearly an important one for a significant fraction of our prospective audience. In response to that demand, we went back to the drawing board to see if we could fashion a synthesis of the sort we had found for the acm.graphics package described in Chapter 5. After experimenting with several designs, we were able to engineer an acm.gui package that simplifies the creation and layout of GUI components in a way that is simple for novices and, at the same time, allows for a straightforward transition to the standard Java layout managers. This chapter describes the final design of that package, along with the general principles that led the Task Force to adopt it.
Although the design of the acm.gui package is largely independent of the acm.program package described in Chapter 6, we expect that most adopters will use the GUI tools in the context of a Program object. The examples in this chapter therefore are designed to use acm.program as their application framework. Adapting those examples so that they use a JFrame object instead of a Program is straightforward, but is not spelled out in detail.
In many ways, developing a simple GUI package for Java is difficult not because Javas existing libraries are lacking essential functionality but rather because they contain so much that is good. A large fraction of the classes in the javax.swing package are specifically designed to support the development of graphical user interfaces and work well at that task. The collection of GUI widgets available in Swing is extraordinarily rich and, moreover, seems on the whole to be well designed. Students have access to buttons, scrollbars, sliders, text areas, selectable lists, popup menus, and a host of other interactor classes that enable them to write graphical user interfaces containing most if not all of the interactor patterns they are likely to see in professional software. And while it may require some sophistication to use the more advanced features of these interactors, most of the Swing classes specify reasonable defaults that make it easy to use these interactors in conventional ways. Novices, for example, have little trouble using
At the same time, educators had certainly complained about the inappropriateness of Javas GUI components for novices. Clearly, something was missing. The important question for the Task Force was to figure out what those missing elements might be.
As was the case in our design of the acm.graphics package, the Java Task Force identified strengths in each of these proposals, but felt that none were suitable as they stood. The common problem in each of these packages was that they differed much more substantially from the Swing standard than we were prepared to accept. We also found the strongest features of each package occurred in complementary aspects of their designs, making it possible to create an improved package by adopting the best features of each.
The important realization that we derived from studying these proposals in detail is that the critical problem does not lie with the Swing interactor classes themselves, but rather with the facilities that the java.awt package uses for placing those interactors on the screen, which is largely unchanged in the Swing world. Richard Rasala offers the following succinct expression of this principle in the introduction to one of his proposals to the Task Force:
A central problem in GUI construction is the composition of the individual GUI widgets into some organized arrangement within a panel or frame. To avoid manual pixel positioning, Java uses layout managers to layout components. Unfortunately, the layout managers provided by Java are very problematic. The simple ones do not do enough and the advanced ones are very hard to use and often do not produce the expected results after all the work.[Rasala04c]
The essential problem can be expressed more precisely as follows: Given the standard facilities available in Javas AWT and Swing packages, there is no good way to create a two-dimensional layout in which the preferred sizes of the components determine the sizes of the cells in the grid. The problem is not that the necessary functionality is unavailable but rather that the standard classes are difficult for novices to use. Javas GridBagLayout class provides the necessary capability, but is not suitable for novices for the reasons described in the following section.
The most significant problems associated with GridBagLayout are as follows:
|
|
Given these problems, the general desiderata for a new class to replace GridBagLayout are for the most part clear. To be successful, the new class should
The first three design criteria follow immediately from the principles that the Task Force articulated in Chapter 2. The last design goal, by contrast, is not necessarily as obvious. The external submissions we received typically eliminated some functionality from GridBagLayout as a way of simplifying it. Our concern, however, was that such an approach would give some potential adopters a reason for staying with GridBagLayout, despite its shortcomings. If the Task Force packages were incapable of duplicating a layout constraint that someone was already using, that person would be less likely to adopt the alternative design. If, on the other hand, one can do everything with the new class that had previously been possible with GridBagLayout, much of the resistance to change could be eliminated.
Our solution was to implement a TableLayout manager class that offers the functionality of GridBagLayout in a way that is much easier for novices to understand. Its motivations are therefore similar to those that gave rise to the BreezySwing package
As noted in the preceding section, the Task Force decided that it was necessary to provide IntField and DoubleField classes to simplify the development of applications that require numeric input. Each of these classes extends JTextField but provides additional methods to hide the complexity involved in numeric conversion and exception handling. The additional methods available for DoubleField appear in Figure 7-1; the methods for IntField are the same except for the expected changes in the argument and result types.
Figure 7-1. Methods defined in the DoubleField class
|
The IntField and DoubleField classes are closely related to a similar pair of classes proposed by Lambert and Osborne
The only significant design decision for these classes was how to control format. It turns out that the DoubleField class is useful in practice only if the client has some way of specifying the format of the displayed result. In the absence of format control, the display typically contains so many digits as to become unreadable. The setFormat and getFormat methods shown in Figure 7-1 eliminate this problem by allowing the client to specify the output format. The format itself is specified using a string as defined in the DecimalFormat class in java.text.
Figure 7-2. Temperature conversion program
/* * File: TemperatureConverter.java * ------------------------------- * This program allows users to convert temperatures * back and forth from Fahrenheit to Celsius. */ import acm.gui.*; import acm.program.*; import java.awt.event.*; import javax.swing.*; /** This class implements a temperature converter. */ public class TemperatureConverter extends Program { /** Initialize the graphical user interface. */ public void init() { setLayout(new TableLayout(2, 3)); fahrenheitField = new IntField(32); fahrenheitField.setActionCommand("F -> C"); fahrenheitField.addActionListener(this); celsiusField = new IntField(0); celsiusField.setActionCommand("C -> F"); celsiusField.addActionListener(this); add(new JLabel("Degrees Fahrenheit")); add(fahrenheitField); add(new JButton("F -> C")); add(new JLabel("Degrees Celsius")); add(celsiusField); add(new JButton("C -> F")); addActionListeners(); } /** Listen for a button action. */ public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if (cmd.equals("F -> C")) { int f = fahrenheitField.getValue(); int c = (int) Math.round((5.0 / 9.0) * (f - 32)); celsiusField.setValue(c); } else if (cmd.equals("C -> F")) { int c = celsiusField.getValue(); int f = (int) Math.round((9.0 / 5.0) * c + 32); fahrenheitField.setValue(f); } } /* Private instance variables. */ private IntField fahrenheitField; private IntField celsiusField; } |
The user interface for the TemperatureConverter program looks like this:
The user can type values into either of the IntField interactors and then perform the conversion to the other scale either by hitting the appropriate button or by hitting the Enter key in the interactor itself. Each of these actions generates an ActionEvent whose action command is either the string "F -> C" or "C -> F" depending on which button or interactor generated the event. These events are fielded by the actionPerformed method in the class, which performs the necessary conversion and then updates the value of the corresponding field.
In terms of understanding what the TableLayout framework provides, all of the important code appears in the constructor. The line
|
defines the layout for the program as a whole to be a TableLayout object with two rows and three columns. The rest of the constructor adds interactors to the table layout in order, filling each horizontal row from left to right, and then proceeding through each row from top to bottom. In this example, each row contains a JLabel indicating the temperature scale for that row, an IntField that allows the user to enter a value, and a JButton to trigger the conversion. The code that sets the action command for the IntField and adds the program as a listener is required only to enable the Enter key in those interactors. The buttons would be active even in the absence of those calls.
The feature that sets TableLayout apart from the simpler GridLayout mechanism is that the sizes of each component in the table are adjusted according to their preferred sizes and the constraints imposed by the grid. The JLabel objects are of different sizes, but the implementation of TableLayout makes sure that there is enough space in the first column to hold the longer of the two labels. By default, each component added to a TableLayout container is expanded to fills its grid cell, although this behavior can be changed by specifying constraints as described in the following section.
What the TemperatureConverter program illustrates is that the student can rely on the TableLayout to arrange the elements appropriately on the screen without having to specify the detailed constraint information that would be required using GridBagLayout. By defining the appropriate defaults, all the student usually has to do is count the number of rows and columns in the user interface and add the interactors in the appropriate order.
Instead of allocating a constraints object explicitly, the TableLayout design allows clients to specify the constraints in string form. For every field in Javas GridBagConstraints, the TableLayout manager accepts a constraint string in the form
|
where field is the name of the GridBagConstraints field and value is a value appropriate for that field. For example, to duplicate the effect of setting the gridwidth field of a constraints object to 2 (thereby specifying a two-column field), adopters of the acm.gui package can simply specify the constraint string
|
instead of the more confusing process of allocating a GridBagConstraints object and setting the appropriate field, like this:
|
The strings used as constraint objects can set several fields at once by including multiple field/value pairs separated by spaces. Moreover, for those fields whose values are defined by named constants in the GridBagConstraints class, TableLayout allows that name to be used as the value field of the constraint string. For example, the following string indicates that a field should span two columns but that the component should fill space only in the y direction:
|
Constraint strings are checked at run time to make sure that the fields and values are defined and are consistent. The case of letters, however, is ignored, which makes it possible to name the fields in a way that is consistent with Javas conventions. Thus, if an instructor wants to emphasize the case convention that has each word within a multiword identifier begin with an uppercase letter, it is equally effective to write
|
The complete list of constraints supported by the TableLayout class is shown in Figure 7-3. The first block shows the constraints that adopters are likely to use; the second block consists of constraints that are included only to maintain symmetry with the GridBagConstraints class.
Figure 7-3. Constraints supported by the TableLayout class
|
To emphasize the advantage of using the TableLayout model, Figure 7-4 and 7-5 offer two implementations of a program that lays out
As the code for the figures makes clear, the TableLayout version is less than half the size of the GridBagLayout example and is significantly easier to read. At the same time, the underlying discipline is compatible, which should allow users to migrate from one model to the other.
Figure 7-4. Example from the GridBagLayout documentation
/* * File: GridBagExample.java * ------------------------- * This application uses GridBagLayout to construct a * two-dimensional table of buttons. The example is * adapted from the GridBagLayout documentation. */ import java.awt.*; import javax.swing.*; public class GridBagExample extends JPanel { public GridBagExample() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setLayout(gridbag); c.fill = GridBagConstraints.BOTH; addButton("Button0", gridbag, c); addButton("Button1", gridbag, c); addButton("Button2", gridbag, c); c.gridwidth = GridBagConstraints.REMAINDER; addButton("Button3", gridbag, c); addButton("Button4", gridbag, c); c.gridwidth = 3; addButton("Button5", gridbag, c); c.gridwidth = GridBagConstraints.REMAINDER; addButton("Button6", gridbag, c); c.gridwidth = 1; c.gridheight = 2; addButton("Button7", gridbag, c); c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; addButton("Button8", gridbag, c); addButton("Button9", gridbag, c); } private void addButton(String name, GridBagLayout gridbag, GridBagConstraints c) { Button button = new Button(name); gridbag.setConstraints(button, c); add(button); } public static void main(String[] args) { GridBagExample example = new GridBagExample(); JFrame frame = new JFrame("GridBagExample"); Container contentPane = frame.getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(BorderLayout.CENTER, example); frame.setSize(350, 200); frame.setVisible(true); } } |
Figure 7-5. Similar application using TableLayout
/* * File: TableExample.java * ----------------------- * This application uses the ACM TablePanel class to * construct a two-dimensional table of buttons that * matches the one from GridBagExample. */ import acm.gui.*; import java.awt.*; public class TableExample extends Program { public void init() { setLayout(new TableLayout(5, 4)); add(new Button("Button0")); add(new Button("Button1")); add(new Button("Button2")); add(new Button("Button3")); add(new Button("Button4"), "gridwidth=REMAINDER"); add(new Button("Button5"), "gridwidth=3"); add(new Button("Button6")); add(new Button("Button7"), "gridheight=2"); add(new Button("Button8"), "gridwidth=REMAINDER"); add(new Button("Button9"), "gridwidth=REMAINDER"); } } |
|
or by calling the setHgap and setVgap methods on an existing layout. Setting these parameters ensures that each row or column in the table is separated from the next by the specified number of pixels, making it easy to add spacing around the interactors.
Although the hgap and vgap parameters are typically positive, it turns out that the value 1 can be extremely useful in this setting. If you specify 1 as a gap value, each component will be positioned so that it overlaps the preceding one by one pixel. If the components have a one-pixel border, this strategy ensures that the border running between the components will be only one pixel wide. If no gap applied, each component would have a one-pixel border, which would result in a two-pixel divider on the screen.
To make it easier to assemble nested containers hierarchically, the acm.gui package includes three convenience classes that extend JPanel but install an appropriate TableLayout manager. These classes and their constructor patterns appear in Figure 7-6.
Figure 7-6. Convenience classes based on TableLayout
|
The HPanel and VPanel classes make it easy to create complex assemblages of interactors by decomposing them hierarchically into rows and columns. In this respect, they have at least a common purpose with the BoxLayout manager introduced in the javax.swing package. The panel HPanel and VPanel classes, however, offer far more flexibility because they have the full power of the TableLayout class. The BoxLayout manager, by contrast, makes it difficult to do anything except to string together components in a linear form with no control over spacing or format.
Final ReportJuly 31, 2006 |