13.4 Button Events

In addition to creating displays, we would also like to be able to construct GUI’s that are capable of interacting with a user. In this and the following sections we will be examining a variety of ways that we can create interactions with a GUI. We begin with the use of buttons.

Whenever a button is pressed, this produces an event known as an action event. We say that the button object is the source of the event. If some object is interested in knowing that a particular event has occurred, that object must indicate that it wants to be a listener to occurrences of that event. To do so, the object must register as a listener. Each object that can produce events maintains a list of the objects that are listeners to that particular type of event. When an event occurs, all of the objects registered as listeners for that type of event are passed appropriate information about the event. They can then act on this information in whatever way that we wish them to do.

To begin to show how this works, suppose that we have constructed a JButton object called demoButton and that we want an object of the class ButtonResponse to act as a listener for presses of demoButton. To do so, we must do a number of things.

  1. We register an object as a listener by implementing an interface. To listen to a button, the interface that we need to implement is called ActionListener. For our example, we note that ButtonResponse is implementing the ActionListener interface by writing the class header as
    class ButtonResponse implements ActionListener
  2. We must now actually perform this implementation of the ActionListener interface. Recall that to implement an interface, we must provide actual methods for all the methods whose headers are given in the interface. The ActionListener interface requires the implementation of only one method:
    public void actionPerformed (ActionEvent e)
    It is within this method that we would place our code for the action we want to perform whenever demoButton is pressed.
  3. Finally, we need to add a ButtonResponse object to demoButton’s list of listeners by invoking the method with header
    void addActionListener (ActionListener al)
    The implicit object passed to this method is demoButton and the parameter is a ButtonResponse object (which we have made into an ActionListener object through the use of the interface). If we are calling the method from within the ButtonResponse class, the ActionListener object that we pass to the method is the current implicit ButtonResponse object. The call, therefore, would take the form
    demoButton.addActionListener(this);

The next example shows how we can incorporate these ideas into a complete (although fairly trivial) program.

Example 1

This program creates a window containing a single button. Whenever the button is pressed, the background colour of the button changes randomly.

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

public class ButtonColour implements ActionListener
{
   JButton colourButton = new JButton("Change Colour");
   
   public ButtonColour()
   {
      JFrame frame = new JFrame("Button Events");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setSize(300, 100);
      colourButton.addActionListener(this);
      frame.setLayout(new FlowLayout());
      frame.add(colourButton);
      frame.setVisible(true);
   }
   
   public void actionPerformed(ActionEvent e)
   {
      colourButton.setBackground(new Color(strength(), strength(), strength()));
   }
   
   public static int strength()
   {
      return (int)(256 * Math.random());
   }
   
   public static void main(String[] args)
   {
      new ButtonColour();
   }
} 

The window produced by the program should look something like this. Each time that the button is pressed, the colour of the button changes randomly to one of 16 777 216 (2563 = 16 777 216) possible colours.

Note: There is no call to the method actionPerformed. Instead, since the colourButton object is registered as a listener to ActionEvents, a call to the actionPerformed method within the ButtonColour class is made automatically whenever the button called colourButton is pressed.

The parameter of the method actionPerformed is an ActionEvent object called e. This parameter, supplied automatically by Java, contains information about the event that produced the call to actionPerformed. Since our program is only registered as a listener for one event (the pushing of colourButton), we do not need to use the parameter because the only time that this method will be called is when that particular button is pushed. If we have registered for more than one event, then we may need to examine the parameter to determine which event caused the call to actionPerformed. This is illustrated in the next example.

Example 2

The class TriColourFrame defines windows that have three buttons — one for each of the component colours (red, green, or blue). An object of this class will set the background colour of a window to one of these colours whenever the appropriate button is pressed.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class TriColourFrame implements ActionListener
{
   JButton redButton = new JButton("red");
   JButton greenButton = new JButton("green");
   JButton blueButton = new JButton("blue");
   JFrame frame = new JFrame("Tricolour Buttons");
   
   public TriColourFrame ()
   {
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      redButton.addActionListener(this);
      greenButton.addActionListener(this);
      blueButton.addActionListener(this);
      frame.setLayout(new FlowLayout());
      frame.add(redButton);
      frame.add(greenButton);
      frame.add(blueButton);
      frame.setSize(300,100);
      frame.setVisible(true);
   }

   public void actionPerformed (ActionEvent e)
   {
      if (e.getSource() == redButton)
         frame.getContentPane().setBackground(Color.red);
      else if (e.getSource() == greenButton)
         frame.getContentPane().setBackground(Color.green);
      else if (e.getSource() == blueButton)
         frame.getContentPane().setBackground(Color.blue);
      else
         frame.getContentPane().setBackground(Color.black);
   }
   
   public static void main(String[] args)
   {
      new TriColourFrame();
   }
} 

In Example 2, the actions of the constructor in the class TriColourFrame are similar to those of the corresponding constructor in the previous example. The only essential difference is the extra work that must be done because there are now three buttons instead of one.

The actionPerformed method uses the value of the parameter e to decide what action should be taken. The getSource method gives a reference to the JButton that generated the ActionEvent (which button was clicked). The actionPerformed method then sets the frame's backgound to the appropriate colour.

The following example is similar to Example 2 except it shows how to use graphics with buttons.

Example 3

This example will draw a square, rectangle or a circle depending on which button is pressed.

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

public class DrawingShapes implements ActionListener
{
   int choice = 0;
   JButton square = new JButton("Square");
   JButton rectangle = new JButton("Rectangle");
   JButton circle = new JButton("Circle");
   Drawing draw = new Drawing();
   
   public DrawingShapes()
   {
      JFrame frame = new JFrame("Draw Shapes");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      square.addActionListener(this);
      rectangle.addActionListener(this);
      circle.addActionListener(this);
      JPanel panel = new JPanel();
      panel.add(square);
      panel.add(rectangle);
      panel.add(circle);
      frame.add(panel, "North");
      frame.add(draw);
      frame.setSize(500,500);
      frame.setVisible(true);
   }
   
   public void actionPerformed(ActionEvent e)
   {
      if (e.getSource() == square)
         choice = 1;
      else if (e.getSource() == rectangle)
         choice = 2;
      else 
         choice = 3;
      draw.repaint();
   }
   
   class Drawing extends JComponent
   {
      public void paint(Graphics g)
      {
         g.setColor(Color.red);
         if (choice == 1)
            g.fillRect(100,80,300,300);
         else if (choice == 2)
            g.fillRect(50,200,350,100);
         else if (choice == 3)
            g.fillOval(150,100,200,200);
      }
   }
   
   public static void main(String[] args)
   {
      new DrawingShapes();
   }
}  

Here is what the output will look like after the circle button is pressed.

Exercise 13.4

  1. Write a program that uses a flow layout to display a window containing two buttons, one labelled "On" and the other labelled "Off". If a user presses "On", the background colour should be set to white but if a user presses "Off", the background should be set to black.
  2. Modify the program of the previous question so that the labels on the buttons are "Brighter" and "Dimmer". If a user presses "Brighter", the background colour should be set closer to white (if it is not already white) while pressing "Dimmer" moves the background closer to black (if it is not already black). Have each press of a button change the intensity by one-sixteenth of the difference between pure white and pure black.
  3. Modify the code in example 3 to add two buttons labelled "Larger" and "Smaller". Clicking "Larger" will add 5 pixels to the size of the shape that is showing (it will affect any shape shown after as well). Clicking "Smaller" will subtract 5 pixels from the size.