Section 4.4 A Graphical User Interface (GUI)
While command-line interfaces are useful, one of the great advantages of the Java language is that its extensive class library makes it relatively easy to develop applications that employ Graphical User Interfaces (GUIs). GUIs have been around now for many years, since the production of the Macintosh in the early 1980s. Today nearly all the personal computing applications are GUI-based. Therefore, it is important that beginning programmers be able design and write programs that resemble, albeit on a simpler scale, those programs that they use every day. Among other benefits, developing the ability to write GUI programs, like the ones everyone uses today, will make it easier for you to show off your work to others, which might help motivate further interest in learning to program.
In this and subsequent sections, we will develop an extensible GUI model that can be used with either a Java application or an applet. By extensible we mean a model that can be easily adapted and used in a wide variety of programs. GUI programming involves a computational model known as event-driven programming, which means that GUI programs react to events that are generated mostly by the user's interactions with elements in the GUI. Therefore, we will have to learn how to use Java's event model to handle simple events.
Given that this is our first look at some complex topics, we will keep the discussion as simple as possible. This means we will delay discussion of certain issues, which we take up in more depth in ChapterĀ 13.
Subsection 4.4.1 Java's GUI Components
The Java library comes with two separate but interrelated packages of GUI components, the older java.awt
package and the newer javax.swing
package. For the most part, the Swing classes supersede the AWT classes. For example, the java.awt.Button
class is superseded by the javax.swing.JButton
class, and the java.awt.TextField
class is superseded by the javax.swing.JTextField
class. As these examples show, the newer Swing components add an initial 'J' to the names of their corresponding AWT counterparts.
FigureĀ 4.4.1 illustrates how some of the main components appear in a GUI interface. As shown there, a JLabel
is simply a string of text displayed on the GUI, used here as a prompt. A JTextField
is an input element that can hold a single line of text. In this case, the user has input his name. A JTextArea
is an output component that can display multiple lines of text. In this example, it displays a simple greeting. A JButton
is a labeled control element, which is an element that allows the user to control the interaction with the program. In this example, the user will be greeted by the name input into the JTextField
, whenever the JButton
is clicked. As we will learn, clicking on the JButton
causes an event to occur, which leads the program to take the action of displaying the greeting. Finally, all of these components are contained in a JFrame
, which is a top-level container. A container is a GUI component that can contain other GUI components.
The Swing classes are generally considered to be superior to their AWT counterparts. For one thing, Swing components use a sophisticated object-oriented design known as the model-view-controller (MVC) architecture, which gives them much greater functionality than their AWT counterparts. For example, whereas an AWT Button
can only have a string as its label, a Swing JButton
can use an image as a label. (See ChapterĀ 13 for a detailed discussion of the MVC architecture.)
Second, Swing components are written entirely in Java which makes them more portable and enables them to behave the same way regardless of the operating system on which they are run. Because of their portability, Swing components are considered lightweight. By contrast, AWT classes use routines that are implemented in the underlying operating system and are therefore not easily portable. Hence, they are considered heavyweight components. Whereas a Swing JButton
should look and act the same way regardless of platform, an AWT Button
would have a different implementation, and hence a different look and feel, on a Macintosh and on a Windows system. In this book, we will use the new Swing classes in our programs.
Subsection 4.4.2 Class Inheritance: Extending a Superclass
As you recall from Chapter 0, class inheritance is the mechanism by which a class of objects can acquire (inherit) the methods and variables of its superclasses. Just as a horse, by membership in the class of horses, inherits those attributes and behaviors of a mammal, and, more generally, those of an animal, a Java subclass inherits the variables and methods of its superclasses. We sometimes lump together an object's attributes and behaviors and refer to them collectively as its functionality. So we say that an object of a subclass inherits the functionality of all of its superclasses.
By the same token, just as a horse and a cow extend their mammalian attributes and behaviors in their own special ways, a Java subclass extends the functionality of its superclasses in its own special way. Thus, a subclass specializes its superclass.
In ChapterĀ 3, we showed how all classes in the Java hierarchy inherit the toString()
method from the Object
class. The lesson there was that an object in a subclass can either use or override any public
method defined in any of its superclasses. In order to implement GUI programs, we need to look at another way to employ inheritance. In particular, we need to learn how to define a new class by extending an existing class.
We noted in ChapterĀ 2 that unless a class is explicitly defined as a subclass of some other class it is considered implicitly to be a direct subclass of Object
. Thus, the GreeterApp
class that we defined earlier in this chapter is a subclass of Object
. We can make the relationship between GreeterApp
and Object
explicit by using the extends
keyword when we define the GreeterApp
class:
public class GreeterApp extends Object { ... }
Thus, the extends
keyword is used to specify the subclass/superclass relationships that hold in the Java class hierarchy. We sometimes refer to the subclass/superclass relationship as the isa relationship, in the sense that a horse isa mammal, and a mammal isa animal. Thus, the extends
keyword is used to define the isa relationship among the objects in the Java class hierarchy.
A top-level container is a GUI container that cannot be added to another container; it can only have components added to it. FigureĀ 4.4.2 is a class hierarchy that shows the relationships among some of the top-level Swing and AWT classes. For example, the javax.swing.JFrame
class, which represents a top-level window, is a subclass of java.awt.Frame
, and the javax.swing.JPanel
is a subclass of java.awt.Panel
. We can see from this figure that a JFrame
isaFrame
and an Frame
isaWindow
and a Window
isaContainer
. These subclass/superclass relationships are created in their respective class definitions by using the extends
keyword as follows:
public class JFrame extends Frame { ... }
public class Frame extends Window { ... }
public class Window extends Container { ... }
As we will see in the next section, extending a class in this way enables us to create a new class by specializing an existing class.
Subsection 4.4.3 Top-level Windows
Referring again to FigureĀ 4.4.2, notice that all of the Swing components are subclasses of the AWT Container
class. This means that Swing components are Container
s. They inherit the functionality of the Container
class. So Swing components can contain other GUI components. That is why a JButton
can contain an image.
All GUI programs must be contained inside some kind of top-level container. Swing provides three top-level container classes: JFrame
, JApplet
and JDialog
. For our basic GUI, we will use a JFrame
as the top-level window for stand alone applications.
A JFrame
encapsulates the basic functionality of a top-level window. It has what is called a content pane, to which other Swing components, such as buttons and text fields, can be added. Also, it comes with enough built-in functionality to respond to certain basic commands, such as when the user adjusts its size or closes it.
FigureĀ 4.4.3 shows a simple top-level window as it would be displayed on the console. This window has a title ("My GUI"). It is 200 pixels wide, 150 pixels high, and its top-left corner is located at coordinates (100,150) on the console screen. Like in other graphical systems, points on the Java console always given as an ordered pair, (X, Y), with the horizontal coordinate, X, listed first, followed by the vertical coordinate, Y. The horizontal x-axis extends positively from left to right, and the vertical y-axis extends positively from top to bottom.
The class that created and displayed this window is shown in FigureĀ 4.4.4. Note the use of the extends
keyword to define SimpleGUI
as a subclass of JFrame
. As a subclass, SimpleGUI
inherits all of the functionality of a JFrame
( FigureĀ 4.4.6) . That is, it can contain other GUI components. It knows how to resize and close itself, and so on. The reason we want to define a subclass of JFrame
, rather than just use a JFrame
instance, is because we want eventually to give our subclass additional functionality that is specialized for our application.
Principle 4.4.5. EFFECTIVE DESIGN:Specialization.
By creating a subclass of JFrame
we can specialize its functionality for our application.
Note how SimpleGUI
's main()
program creates an instance of SimpleGUI
by invoking its constructor. There is no need to use a variable here because there are no further references to this object in this class. However, simply constructing a SimpleGUI
will not cause it to appear on the Java console. For that to happen, it is necessary to give it a size and to call its setVisible()
method. This is done in the constructor method.
The constructor method illustrates how to use some of the methods inherited from JFrame
. FigureĀ 4.4.6 shows some of the methods that SimpleGUI
inherits from JFrame
. We use the setSize()
and setLocation()
methods to set SimpleGUI
's size and location. We use the setTitle()
method to set its title. And we use the setVisible()
method to cause it to appear on the console.
Subsection 4.4.4 GUI Components for Input, Output, and Control
To enable our top-level window to serve as a user interface, it will be necessary to give it some components. FigureĀ 4.4.7 provides an overview of some of the main Swing components. Generally, there are three types of components, which correspond to the three main functions of a user interface: input, output, and control. A JTextField
would be an example of an input component. The user can type text into the text field, which can then be transmitted into the program. A JTextArea
is an example of an output component. The program can display text in the text area. Control components enable the user to control the actions of the program. A JButton
would be an example of a control component. It can be associated with an action that can be initiated whenever the user clicks it. We might also consider a JLabel
to be an output component, because we can use it to prompt the user as to what type of actions to take.
Let's begin by creating a simple user interface, one that enables us to perform basic input, output, and control operations with a minimum of Swing components. This will allow us to demonstrate the basic principles and techniques of user-interface design and will result in a GUI that can be extended for more sophisticated applications. For this example, we will limit our application to that of simply greeting the user, just as we did in designing our command-line interface. That means that the user will be prompted to input his or her name and the program will respond by displaying a greeting ( FigureĀ 4.4.1). We will call our GUI GreeterGUI
, to suggest its interdependence with the same Greeter
computational object that we used with the command-line interface.
For this simple application, our GUI will make use of the following components:
A
JTextField
will be used to accept user input.A
JTextArea
will serve to display the program's output.A
JButton
will allow the user to request the greeting.A
JLabel
will serve as a prompt for theJTextField
.
FigureĀ 4.4.8 shows some of the constructors and public methods for the JTextArea
, JTextField
, JButton
, and JLabel
components. The following code segments illustrate how to use these constructors to create instances of these components:
// Declare instance variables for the components
private JLabel prompt;
private JTextField inField;
private JTextArea display;
private JButton goButton;
// Instantiate the components
prompt = new JLabel("Please type your name here: ");
inField = new JTextField(10); // 10 chars wide
display = new JTextArea(10, 30);// 10 rows x 30 columns
goButton = new JButton("Click here for a greeting!");
For this example, we use some of the simpler constructors. Thus, we create a JTextField
with a size of 10. That means it can display 10 characters of input. We create a JTextArea
with 10 rows of text, each 30 characters wide. We create a JButton
with a simple text prompt meant to inform the user of how to use the button.
Subsection 4.4.5 Adding GUI Components to a Top-Level Window
Now that we know how to create GUI components, the next task is to add them to the top-level window. A JFrame
is a top-level Container
( FigureĀ 4.4.2), but instead of adding the components directly to the JFrame
we have to add them to the JFrame
's content pane, which is also a Container
.
Principle 4.4.9. Content Pane.
GUI Components cannot be added directly to a JFrame
. They must be added to its content pane.
Java's Container
class has several add()
methods that can be used to insert components into the container:
add(Component comp)// add comp to end of container
add(Component comp, int index)// add comp at index
add(String region, Component comp) add comp at region
The particular add()
method to use depends on how we want to arrange the components in the container. The layout of a container is controlled by its default layout manager, an object associated with the container that determines the sizing and the arrangement of its contained components. For a content pane, the default layout manager is a BorderLayout
. This is an arrangement whereby components may be placed in the center of the pane and along its north, south, east, and west borders ( FigureĀ 4.4.10).
Components are added to a border layout by using the add(String
region, Component comp)
method, where the String
parameter specifies either "North," "South," "East," "West," or "Center." For example, to add the JTextArea
to the center of the JFrame
we first create a reference to its content pane and we then add the component at its center:
Container contentPane = getContentPane(); // Get pane
contentPane.add("Center",display); // Add JTextArea
One limitation of the border layout is that only one component can be added to each area. This is a problem for our example because we want our prompt JLabel
to be located right before the JTextField
. To get around this problem, we will create another container, a JPanel
, and add the prompt, the text field, and the goButton
to it. That way, all of the components involved in getting the user's input will be organized into one panel. We then add the entire panel to one of the areas on the content pane.
JPanel inputPanel = new JPanel();
inputPanel.add(prompt); // Add JLabel to panel
inputPanel.add(inField); // Add JTextField to panel
inputPanel.add(goButton); // Add JButton to panel
contentPane.add("South", inputPanel); // Add to JFrame
The default layout for a JPanel
is FlowLayout
, which means that components are added left to right with the last addition going at the end of the sequence. This is an appropriate layout for this JPanel because it will place the prompt just to the left of the input JTextField
.
Principle 4.4.11. EFFECTIVE DESIGN:Encapsulation.
JPanel
s can be used to group related components in a GUI.
Subsection 4.4.6 Controlling the GUI's Action
Now that we know how to place all the components on the GUI, we need to design the GUI's controls. As mentioned earlier, GUIs use a form of event-driven programming. Anything that happens when you are using a computerāevery keystroke and mouse movementāis classified as an event. As FigureĀ 4.4.12 illustrates, events are generated by the computer's hardware and filtered up through the operating system and the application programs. Events are handled by special objects called listeners. A listener is a specialist that monitors constantly for a certain type of event. Some events, such as inserting a CD in the CD-ROM drive, are handled by listeners in the operating system. Others, such as typing input into a Web page or a Word document, are handled by listeners in a piece of application software, such as a browser or a word processor.
In an event-driven programming model, the program is controlled by an event loop. That is, the program repeatedly listens for events, taking some kind of action whenever an event is generated. In effect, we might portray this event loop as follows:
Repeat forever or until the program is stopped Listen for events If event-A occurs, handle it with event-A-handler If event-B occurs, handle it with event-B-handler ...
The event loop listens constantly for the occurrence of events and then calls the appropriate object to handle each event.
FigureĀ 4.4.13 shows some of the main types of events in the java.awt.event
package. In most cases, the names of the event classes are suggestive of their roles. Thus, a MouseEvent
occurs when the mouse is moved. A KeyEvent
occurs when the keyboard is used. The only event that our program needs to listen for is an ActionEvent
, the type of event that occurs when the user clicks the JButton
.
When the user clicks the JButton
, Java will create an ActionEvent
object. This object contains important information about the event, such as the time that the event occurred and the object, such as a JButton
, that was the locus of the event. For our application, when the user clicks the JButton
, the program should input the user's name from the JTextField
and display a greeting, such as āHi John nice to meet youā in the JTextArea
. That is, we want the program to execute the following code segment:
String name = inField.getText();
display.append(greeter.greet(name) + "\n");
The first line uses the JTextField.getText()
method to get the text that the user typed into the JTextField
and stores it in a local variable, name
. The second line passes the name
to the greeter.greet()
method and passes the result it gets back to the JTextArea.append()
method. This will have the effect of displaying the text at the end of the JTextArea
.
In this example, we have used a couple of the standard public methods of the JTextField
and JTextArea
classes. For our simple GUI, the methods described in FigureĀ 4.4.8 java.sun.com/j2se/1.5.0/docs/api/} will be sufficient for our needs. However, if you would like to see the other methods available for these and other Swing components, you should check Java's online API documentation.
Subsection 4.4.7 The ActionListener Interface
Given that the code segment just described will do the task of greeting the user, where should we put that code segment in our program? We want that code segment to be invoked whenever the user clicks on the goButton
. You know enough Java to understand that we should put that code in a Java method. However, we need a special method in this case, one that will be called automatically by Java whenever the user clicks that button. In other words, we need a special method that the button's listener knows how to call whenever the button is clicked.
Java solves this problem by letting us define a pre-selected method that can be associated with the goButton
. The name of the method is actionPerformed()
and it is part of the ActionListener
interface. In this case, an interface is a special Java class that contains only methods and constants (final variables). It cannot contain instance variables. (Be careful to distinguish this kind of interface, a particular type of Java class, form the more general kind of interface, whereby we say that a class's public methods make up its interface to other objects.) Here's the definition of the ActionListener
interface:
public abstract interface ActionListener
extends EventListener
{ public abstract void actionPerformed(ActionEvent e);}
This resembles a class definition, but the keyword interface
replaces the keyword class
in the definition. Note also that we are declaring this interface to be abstract
. An abstract interface or abstract class is one that contains one or more abstract methods. An abstract method is one that consists entirely of its signature; it lacks an implementationāthat is, it does not have a method body. Note that the actionPerformed()
method in ActionListener
places a semicolon where its body is supposed to be.
Principle 4.4.14. Java Interface.
A Java interface is like a Java class except that it cannot contain instance variables.
Principle 4.4.15. Abstract Methods and Classes.
An abstract method is a method that lacks an implementation. It has no method body.
Declaring a method abstract
means that we are leaving its implementation up to the class that implements it. This way, its implementation can be tailored to a particular context, with its signature specifying generally what the method should do. Thus, actionPerformed()
should take an ActionEvent
object as a parameter and perform some kind of action.
What this means, in effect, is that any class that implements the actionPerformed()
method can serve as a listener for ActionEvent
s. Thus, to create a listener for our JButton
, all we need to do is give an implementation of the actionPerformed()
method. For our program, the action we want to take when the goButton
is clicked, is to greet the user by name. Thus, we want to set things up so that the following actionPerformed()
method is called whenever the goButton
is clicked:
public void actionPerformed(ActionEvent e)
{ if (e.getSource() == goButton)
{ String name = inField.getText();
display.append(greeter.greet(name) + "\n");
}
}
In other words, we place the code that we want executed when the button is clicked in the body of the actionPerformed()
method. Note that in the if-statement we get the source of the action from the ActionEvent
object and check that it was the goButton
.
That explains what gets done when the button is clickedānamely, the code in actionPerformed()
will get executed. But it doesn't explain how Java knows that it should call this method in the first place. To set that up we must do two further things. We must place the actionPerformed()
method in our GreeterGUI
class, and we must tell Java that GreeterGUI
will be the ActionListener
for the goButton
.
The following stripped-down version of the GreeterGUI class illustrates how we put it all together:
public class GreeterGUI extends Frame
implements ActionListener
{ ...
public void buildGUI()
{ ...
goButton = new JButton("Click here for a greeting!");
goButton.addActionListener(this);
...
}
...
public void actionPerformed(ActionEvent e)
{ if (e.getSource() == goButton)
{ String name = inField.getText();
display.append(greeter.greet(name) + "\n");
}
}
...
}
First, we declare that GreeterGUI
implements the ActionListener
interface in the class header. This means that the class must provide a definition of the actionPerformed()
method, which it does. It also means that GreeterGUI
isaActionListener
. So SimpleGUI
is both a JFrame
and an ActionListener
.
Second, note how we use the addActionListener()
method to associate the listener with the goButton
:
goButton.addActionListener(this)
The this
keyword is a self-referenceāthat is, it always refers to the object in which it is used. It's like a person referring to himself by saying āIā. When used here, the this
keyword refers to this GreeterGUI
. In other words, we are setting things up so that the GreeterGUI
will serve as the listener for action events on the goButton
.
Principle 4.4.16. This Object.
The this
keyword always refers to the object that uses it. It is like saying āIā or āme.ā
Subsection 4.4.8 Connecting the GUI to the Computational Object
ListingĀ 4.4.17 gives the complete source code for our GreeterGUI
interface. Because there is a lot going on here, it might be helpful to go through the program carefully even though we have introduced most of its elements already. That will help us put together all of the various concepts that we have introduced.
To begin with, note the several Java packages that must be included in this program. The javax.swing
package includes definitions for all of the Swing components. The java.awt.event
package includes the ActionEvent
class and the ActionListener
interface, and the java.awt
packages contain the Container
class.
Next note how the GreeterGUI
class is defined as a subclass of JFrame
and as implementing the ActionListener
interface. GreeterGUI
thereby inherits all of the functionality of a JFrame
. Plus, we are giving it additional functionality. One of its functions is to serve as an ActionListener
for its goButton
. The ActionListener
interface consists entirely of the actionPerformed()
method, which is defined in the program. This method encapsulates the actions that will be taken whenever the user clicks the goButton
.
The next elements of the program are its four instance variables, the most important of which is the Greeter
variable. This is the variable that sets up the relationship between the GUI and the computational object. In this case, because the variable is declared in the GUI, we say that the GUI uses the computation object, as illustrated in FigureĀ 4.3.11. This is slightly different from the relationship we set up in the command-line interface, in which the computational object uses the interface ( FigureĀ 4.3.1).
The other instance variables are for those GUI components that must be referred to throughout the class. For example, note that the goButton
, inField
, and display
are instantiated in the buildGUI()
method and referenced again in the actionPerformed()
method.
The next element in the program is its constructor. It begins by creating an instance of the Greeter
computational object. It is important to do this first in case we need information from the computational object in order to build the GUI. In this case we don't need anything from Greeter
, but we will need such information in other programs.
We've already discussed the fact that the constructor's role is to coordinate the initialization of the GreeterGUI
object. Thus, it invokes the buildGUI()
method, which takes care of the details of laying out the GUI components. And, finally, it displays itself by calling the pack()
and setVisible()
methods, which are inherited from JFrame
. The pack()
method sizes the frame according to the sizes and layout of the components it contains. The setVisible()
method is what actually causes the GUI to appear on the Java console.
Finally, note the details of the buildGUI()
method. We have discussed each of the individual statements already. Here we see the order in which they are combined. Note that we can declare the contentPane
and inputPanel
variables locally, because they are not used elsewhere in the class.
Exercises Self-Study Exercises
1. GreeterGUI.
There is a simple modification that we can make to GreeterGUI
. The JTextField
can serve both as an input element and as a control element for action events. An ActionEvent
is generated whenever the user presses the Return or Enter key in a JTextField
so that the JButton
can be removed. Of course, it will be necessary to designate the inField
as an ActionListener
in order to take advantage of this feature. Using a development environment that allows GUI creation like replit, make the appropriate changes to the buildGUI()
and actionPerformed()
methods so that the inField
can function as both a control and input element. Call the new class GreeterGUI2
. Paste the link or code of your program here to turn it in.
Subsection 4.4.9 Using the GUI in a Java Application
As you know, a Java application is a stand alone program, one that can be run on its own. We have designed our GUI so that it can easily be used with a Java application. We saw in the previous section that the GUI has a reference to the Greeter
object, which is the computational object. Therefore, all we need to get the program to run as an application is a main()
method.
One way to use the GUI in an application is simply to create an instance in a main()
method. The main()
method can be placed in the GreeterGUI
class itself or in a separate class. Here's an example with the main in a separate class:
public class GreeterApplication
{ public static void main(String args[])
{
new GreeterGUI("Greeter");
}
}
The main()
method creates an instance of GreeterGUI
, passing it a string to use as its title. If you prefer, this same main()
method can be incorporated directly into the GreeterGUI
class.