Programming with Java GUI components

Kim Bruce (3/10/98)

Modified by Andrea Danyluk (3/99, 10/99)

  1. Buttons
    1. Adding buttons to a Frame or Panel
    2. ActionListeners for Buttons
    3. Inner Classes
  2. Other GUI components
    1. Labels
    2. Text Fields
    3. Text Areas
  3. Assignment
Java includes libraries to provide multi-platform support for Graphic User Interface objects. The "multi-platform" aspect of this is that you can write a program on a Macintosh and have the same graphic objects show up when the program is run under UNIX or Windows (modulo some minor problems with arrangements of objects on the screen).

Java's GUI components include labels, text fields, text areas, buttons, etc. The Abstract Windowing Toolkit (AWT) also includes containers which can include these components. Containers include frames (windows), canvases (which are used to draw on), and panels (which are used to group components). Panels and canvases are contained in frames (windows) while buttons and other components can be placed either directly on frames or in panels inside the frames.

These GUI components are automatically drawn whenever the window they are in is drawn. Thus we will not need to explicitly put in commands to draw them.

These GUI components are handled using Java's event model. (Note: the Java 1.1. event model is quite different from the 1.0 event model.)

When a user interacts with a component (clicks on a button, types in a field, chooses from a pop-up menu, etc.), an event is generated by the component that you interact with. For each component of your program, the programmer is required to designate one or more objects to "listen" for events from that component. Thus if your program has a button labelled "start" you must assign one or more objects which will be notified when a user clicks on the button. We begin our discussion of GUI programming in Java with a discussion of buttons in more detail.

Note that Chapter 8 of Core Java goes into a great deal of detail on the Java event model and using GUI components. We will be much less complete here. See the text for more detailed instructions.

Buttons

Button is a class in package java.awt which represents buttons on the screen. The constructor is:
    public Button(String label);
which, when executed, creates a button with "label" printed on it. Generally the button is large enough to display the label. There is also a parameterless constructor that creates an unlabeled button.

Buttons respond to a variety of messages. For instance, you can add an "ActionListener" to a button with the method:

    public void addActionListener(ActionListener listener);
We will discuss how to create ActionListeners below.

Adding buttons to a Frame or Panel

We can add a button to a frame or panel by sending the message:
    myFrame.add(startButton);
Normally we include such code in the constructor for the frame or panel. Hence usually we just write this.add(startButton) or simply add(startButton).

Below is some sample code for a class, ButtonDemo, representing a specialization of Frame with two buttons. The class extends Frame, which is part of java.awt. The constructor for Frame takes a String parameter and creates a window with that string in the title bar. The constructor for ButtonDemo calls the superclass constructor, and then sets the size of the new Frame to be 400 x 200. The setLayout command tells the new Frame that new components should be added from left to right across the panel. If it runs out of space new components will be added in new rows, again from left to right. The code for creating buttons should be self-explanatory. The add commands add the buttons to the frame. We explain the ButtonListener class below. It creates objects which perform actions in response to button clicks. Thus we tell the two buttons (with the addActionListener method) to notify myButtonListener when the user clicks on them. Finally, note that we had to import java.awt.* because Frame, Button, and FlowLayout are classes in the java.awt package.

import java.awt.*;

public class ButtonDemo extends Frame
{
    protected Button startButton, stopButton;
    
    // Constructor sets features of the frame, creates buttons, 
    // adds them to the frame, and assigns an object to listen 
    // to them
    public ButtonDemo()
    {
        super("Button demo");       // calls Frame constructor 
                                    // which adds title to window
        setSize(400,200);           // sets the size of the window
        
        // sets layout so objects added go from left to right
        // until fill up row and then go to next row.
	// There are other layout options, including GridLayout.
        setLayout(new FlowLayout());
        
        // create two new buttons w/labels start and stop
        startButton = new Button("Start");  
        stopButton = new Button("Stop");
        
        add(startButton);   // add buttons to frame
        add(stopButton);
        
        // create an object to listen to both buttons:      
        ButtonListener myButtonListener = new ButtonListener();

        // tell buttons that myButtonListener should be notified
        startButton.addActionListener(myButtonListener);
        stopButton.addActionListener(myButtonListener);
	setVisible(true);    // Show the window on the screen.
	                     // Leave this out and you won't see it!
    }
            
    // Trivial main program associated with ButtonDemo
    // Simply creates it and shows it.
    public static void main(String args[]){
        // Create an instance of Buttons
        ButtonDemo app = new ButtonDemo();  
    }
}

The main program associated with the class simply creates an object from the class, which will display itself on the screen. Main programs associated with frames almost always do this minimal amount of work.

ActionListeners for Buttons

Objects which implement the ActionListener interface are required to implement a method actionPerformed which takes a parameter of type ActionEvent. When an action occurs to a button, all objects which have been added as ActionListener's for that button are notified by calling their actionPerformed method with a parameter with information on the exact event that occurred. Thus the system automatically creates the ActionEvent object and sends it along to the listener. You are unlikely to ever need to manually create an ActionEvent object in this course.

The most useful methods of ActionEvent are

    public String getActionCommand();  
and
    public Object getSource();  
If you send getActionCommand to an ActionEvent representing a click of a button, it will return a string corresponding to the label of the button. getSource returns a reference to the object where the event occurred. The class ButtonListener below implements ActionListener, and hence an object of this class can be added to a button as a listener. The actionPerformed method is very simple. It uses getActionCommand to obtain the label of the button clicked and then depending on what the button was, prints a different message. (Recall that the same ActionListener was added to each of the two buttons in ButtonDemo, so getActionCommand needs to figure out which one was clicked.)

Note that because this class is so simple (it doesn't even have instance variables) it needs no explicit constructor. Instead, like all classes it has a default constructor (with no parameters) that just allocates storage for new objects constructed. Note that we must import the java.awt.event package because ActionEvent and the ActionListener interface are both in that package.

import java.awt.event.*;

public class ButtonListener implements ActionListener{

    public void actionPerformed(ActionEvent evt) 
    {
        // Get label of the button clicked in event passed in
        String arg = evt.getActionCommand();    
        if (arg.equals("Start"))
            System.out.println("Start button");
        else if (arg.equals("Stop")) 
            System.out.println("Stop button");
    }
}

Inner Classes

It may seem a bit heavy to have to create a completely separate class (which goes in a separate file) in order to create a listener for the two buttons in our ButtonDemo class.

Two alternatives are possible. One is to let the frame itself be the ActionListener for the button. We can do this by changing the original class header to read:

    public class ButtonDemo extends Frame implements ActionListener
We must also copy the actionPerformed method from class ButtonListener, and finally, we change the two statements adding the ActionListener to have the object itself do the listening:

     // tell buttons that this object should be notified
     startButton.addActionListener(this);
     stopButton.addActionListener(this);

This is the style suggested in Core Java for handling action events in simple cases. The advantage is that now we only need one class instead of two. However, it is generally good design to separate the code for building and handling GUI components from the code which actually does the work spawned by the action events. Moreover, there is another style which is almost as simple, but more general. It involves the use of what are called "inner classes".

Java 1.1 allows classes to be nested in each other. Often when dealing with events we will have a special class which is so special that it will really only ever be used in conjunction with another class. Rather than making this auxiliary class public, we can stick it inside of the other class. Thus we could include the ButtonListener class inside of ButtonDemo. The code now would have the following structure:

import java.awt.*;
import java.awt.event.*;

public class ButtonDemo extends Frame
{
    protected Button startButton, stopButton;
    
    public ButtonDemo()
    {
        ...
        // create an object to listen to both buttons:      
        ButtonListener myButtonListener = new ButtonListener();

        // tell buttons that myButtonListener should be notified
        startButton.addActionListener(myButtonListener);
        stopButton.addActionListener(myButtonListener);
    }
            
    public static void main(String args[]){
        ... // As before
    }   

    protected class ButtonListener implements ActionListener{

    public void actionPerformed(ActionEvent evt) 
    {
        // Get ref to the button clicked in event passed in
        Object source = evt.getSource();    
        if (source == startButton)
            System.out.println("Start button");
        else if (source == stopButton)
            System.out.println("Stop button");
    }

    }
}

Notice that ButtonListener is now declared to be protected, meaning that it can only be used inside the containing class, ButtonDemo. The method actionPerformed is still public, however. If it were declared protected, then it would only be available from within ButtonListener and not within ButtonDemo (and we need to use it in the constructor for ButtonDemo).

These nested classes would now be contained within a single file named ButtonDemo.java. Another advantage of using nested classes is that all of the instance variables (and methods) of the outer class are visible inside the inner class. This can be handy for picking up information from other components of the outer class.

Note in particular that we can now check for the actual source of the event rather than for the label on the button.

Other GUI components

Labels

A Label is a very simple component which contains a string. The constructors are
    public Label()              // creates label with no text
    public Label(String text)   //create label with text
The methods available are
    public String getText()         // return label text
    public void setText(String s)   // sets the label text
However, we rarely change the text in Labels.

Text Fields

A TextField is a single line area that the user can type into. It is a good way of getting text input from the user. The constructors are
    public TextField ()             // creates text field with no text
    public TextField (int columns) 
            // create text field with appropriate # of columns
    public TextField (String s)     // create text field with s displayed
    public TextField (String s, int columns) 
            // create text field with s displayed & approp. width
Methods
    public void setEditable(boolean s) 
           // if false the TextField is not user editable
    public String getText()         // return label text
    public void setText(String s)   // sets the label text
Many other methods are also available for this component (see also the documentation for its superclass, TextComponent).

When the user types into a text field and then hits the return or enter key, it generates an event which can be handled by the same kind of ActionListener used with Buttons. However, in this case there is no useful String to be returned from getActionCommand. Here we use the method getSource. Thus in order to respond to the user's hitting of the return key while typing in a TextField myField, we can write:

    public void actionPerformed(ActionEvent evt) 
    {
        if(evt.getSource() == myField){ 
            String contents = myField.getText();
            System.out.println("The field contained: "+contents);
        } else ...
    }
Notice that this use of myField in actionPerformed only makes sense if we either have an inner class (and hence have access to all of the instance variables of the outer class) or if the Frame or Panel where myField is defined is the associated ActionListener.

If for some reason you'd like to be notified every time any change is made to the TextField (whether or not the user hit the return key), one can associate a TextListener to the field. See details in the on-line documentation.

Text Areas

TextArea is a class that provides an area to hold multiple lines of text. It is fairly similar to TextField except that no special event is generated by hitting the return key.

The constructors are

    public TextArea(int rows, int columns)
       // create text area with appropriate # rows and columns
    public TextArea(String s, int rows, in columns)
       // create text area with rows, columns, and displaying s
Methods
   public void setEditable(boolean s)
      // if false the TextArea is not user editable
   public String getText()  // return text in TextArea
   public void setText(String s)  // sets the text

Assignment

Your assignment for this week is to create a simple additive calculator. Your calculator should have buttons for the digits 0-9, a "+/=" button and a "clear" button. The calculator should also have a TextField in which to display numbers the user has entered and results of their calculations. The layout of the graphical components is entirely up to you.

The calculator should allow the user to add non-negative integers. The "clear" button should clear the display and allow the user to begin a new series of additions. A demo can be found on Cider Press in the Assignments folder in CS 136.

Please have your class implementing ActionListener be an inner class as illustrated above.

This assignment should be turned in by Friday, March 17 at 4:00 PM, though I hope that many of you will be able to finish it in lab.

Because this week's program should be significantly easier than usual, it will count for 15 points rather than the usual 25.