Principle 13.5.1. EFFECTIVE DESIGN: User Interface.
A user interface must effectively perform the tasks of input, output, control, and guidance.
JLabel
s to guide and prompt the user, JTextField
s and JTextArea
s as basic input and output devices, and either JButton
s or JTextField
s for user control.JLabel
, JTextField
, JTextArea
, and JButton
.MetricConverter
class that can be used to perform the conversions (Fig. 13.9). For now at least, this class’s only task will be to convert miles to kilometers, for which it will use the formula that 1 kilometer equals 0.62 miles:public class MetricConverter {
public static double milesToKm(double miles) {
return miles / 0.62;
}
}
double
as input and returns a double
. Also, by declaring the method static
, we make it a class method, so it can be invoked simply byMetricConverter.milesToKm(10);
JLabel
is a display area for a short string of text, an image, or both. Its AWT counterpart, the Label
, cannot display images. A JLabel does not react to input. Therefore, it is used primarily to display a graphic or small amounts of static text. It is perfectly suited to serve as a prompt, which is what we will use it for in this interface.JTextField
is a component that allows the user to edit a single line of text. It is identical to its AWT counterpart, the TextField
. By using its getText()
and setText()
methods, a JTextField
can be used for either input or output, or both. For this problem, we’ll use it to perform the interface’s input task.JTextArea
is a multiline text area that can be used for either input or output. It is almost identical to the AWT TextArea
component. One difference, however, is that a JTextArea
does not contain scrollbars by default. For this program, we’ll use the JTextArea
for displaying the results of conversions. Because it is used solely for output in this program, we’ll make it uneditable to prevent the user from typing in it.JButton
as our main control for this interface. By implementing the ActionListener
interface we will handle the user’s action events.JApplet
. For Java applications, you would typically use a JFrame
as the top-level window. Both of these classes are subclasses of Container
, so they are suitable for holding the components that make up the interface (Fig. Figure 13.2.1).JApplet
s and JFrame
s are both examples of heavyweight components, so they both have windows associated with them. To display a JFrame
we just have to give it a size and make it visible. Because a frame runs as a stand-alone window, not within a browser context, it should also be able to exit the application when the user closes the frame.JFrame
. The prompt, input text field, and control button are arranged in a row above the text area. This is a simple and straightforward layout.JFrame
.Converter
class, which extends the JFrame
class and implements the ActionListener
interface. As a JFrame
subclass, a Converter
can contain GUI components. As an implementor of the ActionListener
interface, it also will be able to handle action events through the actionPerformed()
method.Converter
class. Note the three packages that are imported. The first contains definitions of the Swing classes, and the other two contain definitions of AWT events and layout managers that are used in the program.JFrame
’s layout to FlowLayout
. A layout manager is the object that is responsible for sizing and arranging the components in a container so that the elements are organized in the best possible manner. A flow layout is the simplest arrangement: The components are arranged left to right in the window, wrapping around to the next “row” if necessary.JFrame
. Instead of adding components directly to the JFrame
, we must add them to its content pane:getContentPane().add(input);
JPanel
that serves as the working area of the JFrame
. It contains all of the frame’s components. If you want to add a component directly to a JFrame
, you must first set a LayoutManager
. The ContentPane
has a default BorderLayout
manager.JFrame
and all the other top-level Swing windows have an internal structure made up of several distinct objects that can be manipulated by the program. Because of this structure, GUI elements can be organized into different layers within the window to create many types of sophisticated layouts. Also, one layer of the structure makes it possible to associate a menu with the frame.Converter
frame is instantiated, made visible, and eventually exited in the application’s main()
method:public static void main(String args[]) {
Converter f = new Converter();
// set size width x height
f.setSize(400, 300);
// show the JFrame
f.setVisible(true);
// close when x button is clicked
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} // main()
FlowLayout
, it is especially important to give the frame an appropriate size. Failure to do so can cause the components to be arranged in a confusing way and might even cause some components to not appear in the window. These are limitations we will fix when we learn how to use some of the other layout managers.JTextArea
, so there’s no reason not to clear the input text field:input.setText(""); // Clear the input field
JTextField
and the JButton
serve as controls. That way, to get the program to do the conversion, the user can just press the Enter key after typing a number into the text field.ActionListener
to the JTextField
during the initialization step:input.addActionListener(this);
JTextField
generates an ActionEvent
whenever the Enter key is pressed. We don’t even need to modify the actionPerformed()
method, since both controls will generate the same action event. This will allow users who prefer the keyboard to use just the keyboard.milesToKm()
method below to fix this problem.Math.round()
JButton
s can be used as the keypad’s keys. As a user clicks keypad buttons, their face values—0 through 9—are inserted into the text field. The keypad will also need a button to clear the text field and one to serve as a decimal point.JButton
components to our interface. Instead of inserting them into the JFrame
individually, it will be better to organize them into a separate panel and to insert the entire panel into the frame as a single unit. This will help reduce the complexity of the display, especially if the keypad buttons can be grouped together visually. Instead of having to deal with 16 separate components, the user will see the keypad as a single unit with a unified function. This is an example of the abstraction principle, similar to the way we break long strings of numbers (1-888-889-1999) into subgroups to make them easier to remember.JButton
s are contained within a JPanel
. In the frame’s layout, the entire panel is inserted just after the text area.KeyPad
class (Figure 13.5.12). The KeyPad
will be a subclass of JPanel
and will handle its own ActionEvent
s. As we saw in Chapter 4, a JPanel
is a generic container. It is a subclass of Container
via the JComponent
class ( Figure 13.2.2). Its main purpose is to contain and organize components that appear together on an interface.
JPanel
to hold the keypad buttons. As you might recall from Chapter 4, to add elements to a JPanel
, you use the add()
method, which is inherited from Container
. (A JApplet
is also a subclass of Container
via the Panel
class.)JPanel
, the KeyPad
will take care of holding and organizing the JButton
s in the visual display. We also need some way to organize and manage the 12 keypad buttons within the program’s memory. Clearly, this is a good job for an array. Actually, two arrays would be even better, one for the buttons and one for their labels:private JButton buttons[];
private String labels[] = // An array of button labels
{ "1","2","3",
"4","5","6",
"7","8","9",
"C","0","." };
label
array stores the strings that we will use as the buttons’ labels. The main advantage of the array is that we can use a loop to instantiate the buttons:buttons = new JButton[NBUTTONS]; // Create the array
// For each labeled button
for(int k = 0; k < buttons.length; k++) {
buttons[k] = new JButton(labels[k]); // Create button
buttons[k].addActionListener(this); // and a listener
add(buttons[k]); // and add it to the panel
} // for
KeyPad()
constructor. It begins by instantiating the array itself. It then uses a for loop, bounded by the size of the array, to instantiate each individual button and insert it into the array. Note how the loop variable here, k, plays a dual role. It serves as the index into both the button array (buttons
) and the array of strings that serves as the buttons’ labels (labels
). In that way the labels are assigned to the appropriate buttons. Note also how each button is assigned an ActionListener
and added to the panel:buttons[k].addActionListener(this); // Add listener
add(buttons[k]); // Add button to panel
KeyPad
object concerns how it will interact with the Converter
that contains it. When the user clicks a keypad button, the key’s label has to be displayed in the Converter
’s text area. But because the text area is private to the converter, the KeyPad
does not have direct access to it. To address this problem, we will use a Java interface to implement a callback design. In this design, whenever a KeyPad
button is pressed, the KeyPad
object calls a method in the Converter
that displays the key’s label in the text area.Converter
and the KeyPad
is bi-directional. This means that each object has a reference to the other and can invoke the other’s public methods. This will be effected by having the Converter
pass a reference to itself when it constructs the KeyPad
:private KeyPad keypad = new KeyPad(this);
KeyPad
needs to know the name of the callback method and the Converter
needs to have an implementation of that method. This is a perfect job for an abstract interface:public abstract interface KeyPadClient {
public void keypressCallback(String s);
}
KeyPad
can interact with any class that implements the KeyPadClient
interface. Note that the KeyPad
has a reference to the KeyPadClient
, which it will use to invoke the keypressCallback()
method.KeyPad
is shown in Listing 13.5.14. Note that its constructor takes a reference to a KeyPadClient
and saves it in an instance variable. Its actionPerformed()
method then passes the key’s label to the KeyPadClient
’s callback method.KeyPad
design, we need to revise our design of the Converter
class ( Figure 13.5.13). The Converter
will now implement the KeyPadClient
interface, which means it must provide an implementation of the keypressCallback()
method:public void keypressCallback(String s) {
if (s.equals("C"))
input.setText("");
else
input.setText(input.getText() + s);
}
KeyPad
object calls the keypressCallback()
method, it passes the label of the button that was pressed. The Converter
object simply appends the key’s label to the input text field, just as if the user typed the key in the text field.KeyPad
(which is a JPanel
) is FlowLayout
, which is not appropriate for a numeric keypad that needs to be arranged into a two-dimensional grid pattern, which is the kind of layout our design called for (Fig. Figure 13.5.11).java.awt.GridLayout
, which is perfectly suited for a two-dimensional keypad layout (Section 13.7.2).