[ Next Section | Chapter Index | Main Index ]

Section 6.1

The Basic GUI Application


The command-line programs that you have learned how to program would seem very alien to most computer users. The style of interaction where the user and the computer take turns typing strings of text seems like something out of the early days of computing, although it was only in the mid 1980s that home computers with graphical user interfaces started to become available. Today, most people interact with their computers exclusively through a GUI. A GUI program offers a much richer type of user interface, where the user uses a mouse and keyboard to interact with GUI components such as windows, menus, buttons, check boxes, text input boxes, scroll bars, and so on.

A GUI program still has a main() subroutine, but in general, the main routine in a Swing application generally just creates one or more GUI components and displays them on the computer screen. Once the GUI components have been created, they follow their own programming—programming that tells them how to draw themselves on the screen and how to respond to events such as being clicked on by the user.

A GUI program doesn't have to be immensely complex. We can, for example, write a very simple GUI "Hello World" program that says "Hello" to the user, but does it by opening a window where the greeting is displayed:

import javax.swing.JOptionPane;

public class HelloWorldGUI1 {
   
   public static void main(String[] args) {
      JOptionPane.showMessageDialog( null, "Hello World!" );
   }

}

When this program is run, a window appears on the screen that contains the message "Hello World!". The window also contains an "OK" button for the user to click after reading the message. When the user clicks this button, the window closes and the program ends. This program can be placed in a file named HelloWorldGUI1.java, compiled, and run using the java command on the command line just like any other Java program.

Now, this program is already doing some pretty fancy stuff. It creates a window, it draws the contents of that window, and it handles the event that is generated when the user clicks the button. The reason the program was so easy to write is that all the work is done by showMessageDialog(), a static method in the built-in class JOptionPane. (Note that the source code "imports" the class javax.swing.JOptionPane to make it possible to refer to the JOptionPane class using its simple name. See Subsection 4.6.3 for information about importing classes from Java's standard packages.)

If you want to display a message to the user in a GUI program, this is a good way to do it: Just use a standard class that already knows how to do the work! And in fact, JOptionPane is regularly used for just this purpose (but as part of a larger program, usually). Of course, if you want to do anything serious in a GUI program, there is a lot more to learn. To give you an idea of the types of things that are involved, we'll look at a short GUI program that does the same things as the previous program—open a window containing a message and an OK button, and respond to a click on the button by ending the program—but does it all by hand instead of by using the built-in JOptionPane class. Mind you, this is not a good way to write the program, but it will illustrate some important aspects of GUI programming in Java.

Here is the source code for the program. You are not expected to understand it yet. I will explain how it works below, but it will take the rest of the chapter before you will really understand completely. In this section, you will just get a brief overview of GUI programming.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class HelloWorldGUI2 {
   
   private static class HelloWorldDisplay extends JPanel {
      public void paintComponent(Graphics g) {
         super.paintComponent(g);
         g.drawString( "Hello World!", 20, 30 );
      }
   }
   
   private static class ButtonHandler implements ActionListener {
      public void actionPerformed(ActionEvent e) {
         System.exit(0);
      }
   }
   
   public static void main(String[] args) {
      
      HelloWorldDisplay displayPanel = new HelloWorldDisplay();
      JButton okButton = new JButton("OK");
      ButtonHandler listener = new ButtonHandler();
      okButton.addActionListener(listener);

      JPanel content = new JPanel();
      content.setLayout(new BorderLayout());
      content.add(displayPanel, BorderLayout.CENTER);
      content.add(okButton, BorderLayout.SOUTH);

      JFrame window = new JFrame("GUI Test");
      window.setContentPane(content);
      window.setSize(250,100);
      window.setLocation(100,100);
      window.setVisible(true);

   }
   
}

6.1.1  JFrame and JPanel

In a Java GUI program, each GUI component in the interface is represented by an object in the program. One of the most fundamental types of component is the window. Windows have many behaviors. They can be opened and closed. They can be resized. They have "titles" that are displayed in the title bar above the window. And most important, they can contain other GUI components such as buttons and menus.

Java, of course, has a built-in class to represent windows. There are actually several different types of window, but the most common type is represented by the JFrame class (which is included in the package javax.swing). A JFrame is an independent window that can, for example, act as the main window of an application. One of the most important things to understand is that a JFrame object comes with many of the behaviors of windows already programmed in. In particular, it comes with the basic properties shared by all windows, such as a titlebar and the ability to be opened and closed. Since a JFrame comes with these behaviors, you don't have to program them yourself! This is, of course, one of the central ideas of object-oriented programming. What a JFrame doesn't come with, of course, is content, the stuff that is contained in the window. If you don't add any other content to a JFrame, it will just display a blank area—or, if you don't set its size, it will be so tiny that it will be hard to find on the screen. You can add content either by creating a JFrame object and then adding the content to it or by creating a subclass of JFrame and adding the content in the constructor of that subclass.

The main program above declares a variable, window, of type JFrame and sets it to refer to a new window object with the statement:

JFrame window = new JFrame("GUI Test");

The parameter (the string "GUI test") in the constructor specifies the title that will be displayed in the titlebar of the window. This line creates the window object, but the window itself is not yet visible on the screen. Before making the window visible, some of its properties are set with these statements:

window.setContentPane(content);
window.setSize(250,100);
window.setLocation(100,100);

The first line here sets the content of the window. (The content itself was created earlier in the main program.) The second line says that the window will be 250 pixels wide and 100 pixels high. The third line says that the upper left corner of the window will be 100 pixels over from the left edge of the screen and 100 pixels down from the top. Once all this has been set up, the window is actually made visible on the screen with the command:

window.setVisible(true);

It might look as if the program ends at that point, and, in fact, the main() routine does end. However, the window is still on the screen and the program as a whole does not end until the user clicks the OK button. Once the window was opened, a new thread was created to manage the graphical user interface, and that thread continues to run even after main() has finished.


The content that is displayed in a JFrame is called its content pane. (In addition to its content pane, a JFrame can also have a menu bar, which is a separate thing that I will talk about later.) A basic JFrame already has a blank content pane; you can either add things to that pane or you can replace the basic content pane entirely. In my sample program, the line window.setContentPane(content) replaces the original blank content pane with a different component. (Remember that a "component" is just a visual element of a graphical user interface.) In this case, the new content is a component of type JPanel.

JPanel is another of the fundamental classes in Swing. The basic JPanel is, again, just a blank rectangle. There are two ways to make a useful JPanel: The first is to add other components to the panel; the second is to draw something in the panel. Both of these techniques are illustrated in the sample program. In fact, you will find two JPanels in the program: content, which is used to contain other components, and displayPanel, which is used as a drawing surface.

Let's look more closely at displayPanel. This variable is of type HelloWorldDisplay, which is a static nested class inside the HelloWorldGUI2 class. (Nested classes were introduced in Section 5.8.) This class defines just one instance method, paintComponent(), which overrides a method of the same name in the JPanel class:

private static class HelloWorldDisplay extends JPanel {
   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.drawString( "Hello World!", 20, 30 );
   }
}

The paintComponent() method is called by the system when a component needs to be painted on the screen. In the JPanel class, the paintComponent method simply fills the panel with the panel's background color. The paintComponent() method in HelloWorldDisplay begins by calling super.paintComponent(g). This calls the version of paintComponent() that is defined in the superclass, JPanel; that is, it fills the panel with the background color. (See Subsection 5.6.2 for a discussion of the special variable super.) Then it calls g.drawString() to paint the string "Hello World!" onto the panel. The net result is that whenever a HelloWorldDisplay is shown on the screen, it displays the string "Hello World!".

We will often use JPanels in this way, as drawing surfaces. Usually, when we do this, we will define a class that is a subclass of JPanel and we will write a paintComponent method in that class to draw the desired content in the panel. The subclass of JPanel can be defined either as a separate class in its own file or as a nested class. In simple cases, I will tend to use a nested class for the convenience.


6.1.2  Components and Layout

Another way of using a JPanel is as a container to hold other components. Java has many classes that define GUI components. Except for top-level components like windows, components must be added to a container before they can appear on the screen. In the sample program, the variable named content refers to a JPanel that is used as a container. Two other components are added to that container. This is done in the statements:

content.add(displayPanel, BorderLayout.CENTER);
content.add(okButton, BorderLayout.SOUTH);

Here, content refers to an object of type JPanel; later in the program, this panel becomes the content pane of the window. The first component that is added to content is displayPanel which, as discussed above, displays the message, "Hello World!". The second is okButton which represents the button that the user clicks to close the window. The variable okButton is of type JButton, the Java class that represents push buttons.

The "BorderLayout" stuff in these statements has to do with how the two components are arranged in the container. When components are added to a container, there has to be some way of deciding how those components are arranged inside the container. This is called "laying out" the components in the container, and the most common technique for laying out components is to use a layout manager. A layout manager is an object that implements some policy for how to arrange the components in a container; different types of layout manager implement different policies. One type of layout manager is defined by the BorderLayout class. In the program, the statement

content.setLayout(new BorderLayout());

creates a new BorderLayout object and tells the content panel to use the new object as its layout manager. Essentially, this line determines how components that are added to the content panel will be arranged inside the panel. We will cover layout managers in much more detail later, but for now all you need to know is that adding okButton in the BorderLayout.SOUTH position puts the button at the bottom of the panel, and putting displayPanel in the BorderLayout.CENTER position makes it fill any space that is not taken up by the button.

This example shows a general technique for setting up a GUI: Create a container and assign a layout manager to it, create components and add them to the container, and use the container as the content pane of a window. A container is itself a component, so it is possible that some of the components that are added to the top-level container are themselves containers, with their own layout managers and components. This makes it possible to build up complex user interfaces in a hierarchical fashion, with containers inside containers inside containers...


6.1.3  Events and Listeners

The structure of containers and components sets up the physical appearance of a GUI, but it doesn't say anything about how the GUI behaves. That is, what can the user do to the GUI and how will it respond? GUIs are largely event-driven; that is, the program waits for events that are generated by the user's actions (or by some other cause). When an event occurs, the program responds by executing an event-handling method. In order to program the behavior of a GUI, you have to write event-handling methods to respond to the events that you are interested in.

The most common technique for handling events in Java is to use event listeners. A listener is an object that includes one or more event-handling methods. When an event is detected by another object, such as a button or menu, the listener object is notified and it responds by running the appropriate event-handling method. An event is detected or generated by an object. Another object, the listener, has the responsibility of responding to the event. The event itself is actually represented by a third object, which carries information about the type of event, when it occurred, and so on. This division of responsibilities makes it easier to organize large programs.

illustration of event being generated and sent to listener

As an example, consider the OK button in the sample program. When the user clicks the button, an event is generated. This event is represented by an object belonging to the class ActionEvent. The event that is generated is associated with the button; we say that the button is the source of the event. The listener object in this case is an object belonging to the class ButtonHandler, which is defined as a nested class inside HelloWorldGUI2:

private static class ButtonHandler implements ActionListener {
   public void actionPerformed(ActionEvent e) {
      System.exit(0);
   }
}

This class implements the ActionListener interface—a requirement for listener objects that handle events from buttons. (Interfaces were introduced in Section 5.7.) The event-handling method is named actionPerformed, as specified by the ActionListener interface. This method contains the code that is executed when the user clicks the button; in this case, the code is simply a call to System.exit(), which will terminate the program.

There is one more ingredient that is necessary to get the event from the button to the listener object: The listener object must register itself with the button as an event listener. This is done with the statement:

okButton.addActionListener(listener);

This statement tells okButton that when the user clicks the button, the ActionEvent that is generated should be sent to listener. Without this statement, the button has no way of knowing that there is something that would like to listen for events from the button.

This example shows a general technique for programming the behavior of a GUI: Write classes that include event-handling methods. Create objects that belong to these classes and register them as listeners with the objects that will actually detect or generate the events. When an event occurs, the listener is notified, and the code that you wrote in one of its event-handling methods is executed. At first, this might seem like a very roundabout and complicated way to get things done, but as you gain experience with it, you will find that it is very flexible and that it goes together very well with object oriented programming.

Now, in fact, ActionListener is a functional interface, since it defines just one method, and it can be used to define lambda expressions (see Section 4.5). This means that we could use a lambda expression as the event handler for an action event. In the sample program, we could eliminate the nested subclass ButtonHandler and the variable listener of type ButtonHandler and set up the event handling with the statement

okButton.addActionListener( e -> System.exit(0) );

The parameter e is required, even though its value is not used, because the function defined by the ActionListener interface requires a parameter. See HelloWorldGUI3.java for a complete program that uses this technique. Unfortunately, lambda expressions cannot be used for all event handling in Swing, since most interfaces in Swing are not functional interfaces.

This section has introduced some of the fundamentals of GUI programming with Swing. We will spend the rest of the chapter exploring them in more detail.


[ Next Section | Chapter Index | Main Index ]