Event Driven Programming

Writing GUI applications requires that program control be driven by the user's interaction with the GUI. When the user moves the mouse, clicks on a button, or selects an item from a menu an “event” occurs.

Event driven programs contain two specific types of objects:

  • Source objects – GUI objects (e.g., a JButton object) which generates an event (e.g., when the button is clicked).
  • Listener objects – An object that subscribes (listens) for a particular type of event to occur.

The operating system or windowing system captures generated events and notifies the listener objects when a type of event they are listening has occurred. The operating system notifies the event listener object by sending a message to (calling) the event handling method. If no event listener object is subscribed to listen for the event that occurred, the event is ignored.

Types of Events

There are many different kinds of events

Event Trigger Event Listener Type
Click a button ActionListener
Press Enter while typing in a text field ActionListener
Choose a menu item ActionListener
Close a frame (main window) WindowListener
Press mouse button while hovering over a component MouseListener
Move mouse over a component MouseMotionListener
Component becomes visible ComponentListener
Component gets keyboard focus FocusListener
Table/List selection changes ListSelectionListener
Any property in a component changes* PropertyChangeListener

*For example, the text associated with a label in a component changes.

Action Events I

One of the most common type of events is an action event.

  • An Action Listener object is an object that can register to be notified of an action event.
  • The term subscribe is often used to describe the process of registering to be notified.
  • To subscribe an Action Listener object to an Action Event source, the event source's addActionListener method must be called.
  • The addActionListener must be passed a reference to the Action Listener object so that it knows how to notify the listener when an event occurs.
  • The system notifies an Action Listener by calling the listener's actionPerformed() method.
  • The actionPerformed method must be passed a reference to the Action Event object.

Sample Code

Here is an example of a GUI class that is an Action Listener:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
public class GUI implements ActionListener {
 
  private JButton btnQuit;
  private JButton btnHelp;
 
  public static void main(String[] args) {
    GUI myGUI = new GUI();
  }
 
  public GUI() {
 
    JFrame frame = new JFrame("Event handing sample");
    frame.setSize(200, 100);
    frame.setLocation(10, 10);
    frame.setResizable(false);
    frame.setLayout(null);
 
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
    frame.setVisible(true);
 
    btnQuit = new JButton("Quit");
    btnQuit.addActionListener(this);
    btnQuit.setBounds(10, 25, 80, 30);
 
    frame.add(btnQuit);
 
    btnHelp = new JButton("Help");
    btnHelp.addActionListener(this);
    btnHelp.setBounds(100, 25, 80, 30);
 
    frame.add(btnHelp);
 
  }
 
  public void actionPerformed(ActionEvent event)
  {
    System.out.println("You pushed my button");
  }
}

The class contains two Action Sources (btnQuit and btnHelp). In order for the GUI class to be an action listener, it must implement the ActionListener interface. This is accomplished by:

  • Adding implements ActionListener just prior to the openning curly brace for the class, and
  • Implementing the actionPerformed method.

This is not a good way to design your code because the GUI class now serves two purposes: 1) describe the layout of the window and 2) describe the functionality of the components of the window. Next we will attempt to separate the Action Listener from the GUI class.

Identifying the Event Source

The sample above displays “You pushed my button” whenever either button is pressed. In fact, the same message would be displayed even if the something other than an Action Event occurred. We can ensure that the event source was a JButton with the following modification:

public void actionPerformed(ActionEvent event)
{
  if(event.getSource() instanceof JButton) {
    System.out.println("You pushed my button");
  }
}

The getSource() method returns a reference to the source of the event that triggered the call to actionPerformed. The conditional will be true if the source object is an instance of JButton, i.e., if the source object is an object from the JButton class. We can do even better than this though. The following modification allows us to determine exactly which button was the source of the event.

public void actionPerformed(ActionEvent event)
{
  if(event.getSource() instanceof JButton) {
    JButton eventSrc = (JButton)event.getSource();
    if(eventSrc == btnQuit)
    {
      System.out.println("You pushed my QUIT button");
    } else if(eventSrc == btnHelp) {
      System.out.println("You pushed my HELP button");
    }
  }
}
  • Here we first make sure that the event source is a JButton object.
  • Then we create a reference to a JButton object and make it refer to the source object.
    • Note that getSource() can return an instance from any class. We explicitly type cast it here.
    • Because of the first if statement, we know that getSource() will return a reference to a JButton object, but if the source object was not of type JButton, the reference returned would be null.
  • Essentially, the reference eventSrc stores a location in memory where the source object is found.
  • Similarly, btnQuit and btnHelp store the location in memory where the Quit button and Help buttons are found.
  • If the value of eventSrc and btnQuit are the same, then both references refer to the same object, and we know that the source of the event was the Quit button.

Alternatively, we could use getActionCommand() to get the text of the source object instead of getting a reference to the source object. This would look something like this:

public void actionPerformed(ActionEvent event)
{
  if(event.getSource() instanceof JButton) {
    String text = event.getActionCommand();
    if(text.equals("Quit")
    {
      System.out.println("You pushed my QUIT button");
    } else if(text.equals("Help") {
      System.out.println("You pushed my HELP button");
    }
  }
}

We now know how to handle events in our code; however, our GUI class handles two distinct tasks:

  • UI layout
  • Event handling

This is not a good software design. Separating these tasks into to distinct classes is a better design decision.

Also, for GUI classes with a lot of components, the logic associated with the actionPerformed() method could get pretty complicated with lots of if statements required to identify the component that caused the event.

Action Events II

Given the problems identified with implementing the ActionListener in the GUI class, we will explore making separate classes to handle action events. We can replicate the functionality of the example in the previous section by creating an separate action listener class:

import java.awt.event.*;
import javax.swing.JButton;
 
public class ButtonEventHandler implements ActionListener {
 
  private GUI refToGUI;
 
  public ButtonEventHandler(GUI guiRef)
  {
    refToGUI = guiRef;
  }
 
  public void actionPerformed(ActionEvent event)
  {
    if(event.getSource() instanceof JButton) {
      JButton eventSrc = (JButton)event.getSource();
      if(eventSrc == refToGUI.btnQuit)
      {
        System.out.println("You pushed my QUIT button");
      } else if(eventSrc == refToGUI.btnHelp) {
        System.out.println("You pushed my HELP button");
      }
    }
  }
}

Unfortunately, in order for us to make this code work, ButtonEventHandler must be able to access the JButton attributes of the GUI class. We'll need to modify our GUI class as follows:

import javax.swing.*;
 
public class GUI {
 
  public JButton btnQuit;  // ALERT: Bonehead move
  public JButton btnHelp;  // ALERT: Bonehead move
 
  private ButtonEventHandler btnEventHandler;
 
  public static void main(String[] args) {
    GUI myGUI = new GUI();
  }
 
  public GUI() {
    btnEventHandler = new ButtonEventHandler(this);
 
    JFrame frame = new JFrame("Event handing sample");
    frame.setSize(200, 200);
    frame.setLocation(10, 10);
    frame.setResizable(false);
 
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
    frame.setVisible(true);
 
    btnQuit = new JButton("Quit");
    btnQuit.addActionListener(btnEventHandler);
    btnQuit.setBounds(10, 125, 80, 30);
 
    frame.add(btnQuit);
 
    btnHelp = new JButton("Help");
    btnHelp.addActionListener(btnEventHandler);
    btnHelp.setBounds(100, 125, 80, 30);
 
    frame.add(btnHelp);
 
  }
}

The modifications are:

  • Removed the implements ActionListener specification after the class declaration and removed the actionPerformed() method since the ButtonEventHandler class now does this work.
  • Make the btnQuit and btnHelp fields public so that ButtonEventHandler can access them.
  • Added a btnEventHandler attribute that stores a reference to our event handler object.
  • Register our action listener subscriptions with btnEventHandler instead of this (the GUI object).

Notice:

  • If we had a bunch of other GUI components in our GUI class, we could either update the ButtonEventHandler class or we could create another event handler class in much the same way as ButtonEventHandler. For example, we could create MenuEventHandler, to handle menu events. Menu events would subscribe using the menuEventHandler field and button events could subscribe using the btnEventHandler field.
  • We could have left btnQuit and btnHelp private and created getter methods to return references to them, but this too is a bonehead move because in both cases we are exposing the button attributes to everyone. As a result, anyone else who is using our GUI class could come along and mess with our buttons without first getting permission from us.

Action Events III

Action Events II is a step in the right direction, but it violates one of the tenants of Object Oriented programming: don't let others see or mess with your private attributes. We can overcome this weakness by making use of an inner class.

Inner Classes

  • An inner class is a class that is defined inside the same java file (inside the enclosing class).
  • An inner class can access all members of the enclosing class (including those private guys).
  • An inner class can be private to the outside world, i.e., an instance of an inner class cannot be instantiated (created) independently.
  • You can have more than one inner class in an enclosing class

For example:

public class Outer {
 
  private int outerAttr = 0;
  private Inner in;
 
  private class Inner {
    private int innerAttr = 0;
 
    private void doStuff()
    {
      ++innerAttr;
      --outerAttr;
    }
  }
 
  public static void main(String[] args)
  {
    Outer out = new Outer();
    out.displayStuff();
  }
 
  public Outer()
  {
    in = new Inner();
  }
 
  public void displayStuff()
  {
    System.out.println("Inner: " + in.innerAttr + " Outer: " + outerAttr);
    in.doStuff();
    System.out.println("Inner: " + in.innerAttr + " Outer: " + outerAttr);
  }
}

Will produce:

Inner: 0 Outer: 0
Inner: 1 Outer: -1

Sample Code

Making use of the getActionCommand() method of the ActionEvent object passed to actionPerformed(), we no longer need the references to the buttons to be attributes of the class. We can declare them locally in the GUI class's constructor.

To make things slightly more interesting, we'll add a JLabel to the GUI that displays a count of the number of times the Help button has been clicked. Each time the Help button is clicked, the count is incremented and the JLabel is updated. Here is the grand finale:

public class GUI {
 
  private JLabel helpCountLbl;
 
  private class ButtonEventHandler implements ActionListener {
 
    private int helpCount = 0;
 
    @Override
    public void actionPerformed(ActionEvent event) {
      if(event.getSource() instanceof JButton) {
        String actionCommand = event.getActionCommand();
        System.out.println(actionCommand);
        if(actionCommand.equals("Quitter")) {
          System.out.println("You pushed the Quit button");
        } else if(actionCommand.equals("Help")) {
          System.out.println("You pushed my real help button");
          ++helpCount;
          String helpLbl = helpCount == 1 ? " cry " : " cries ";
          helpCountLbl.setText(helpCount + 
              helpLbl + "for help");
        }
      }
    }
  }
 
  public static void main(String[] args) {
    GUI myGUI = new GUI();
  }
 
  public GUI() {
    JButton btnQuit;
    JButton btnHelp;
 
    ButtonEventHandler btnHandler = new ButtonEventHandler();
    JFrame frame = new JFrame("Event handing sample");
    frame.setSize(200, 100);
    frame.setLocation(10, 10);
    frame.setResizable(false);
    frame.setLayout(null);
 
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
    frame.setVisible(true);
 
    btnQuit = new JButton("Quit");
    btnQuit.setActionCommand("Quitter");
    btnQuit.addActionListener(btnHandler);
    btnQuit.setBounds(10, 25, 80, 30);
 
    frame.add(btnQuit);
 
    btnHelp = new JButton("Help");
    btnHelp.addActionListener(btnHandler);
    btnHelp.setBounds(100, 25, 80, 30);
 
    frame.add(btnHelp);
 
    helpCountLbl = new JLabel("No cries for help");
    helpCountLbl.setBounds(10, 50, 180, 30);
 
    frame.add(helpCountLbl);
  }
 
}

Note:

  • The helpCountLbl must be declared as an attribute of the GUI class because it needs to be referenced by both the GUI object and the object of the ButtonEventHandler inner class.
  • We have modified the text associated with the action command for the Quit button by using the setActionCommand.
  • The ternary operator is used to set helpLbl to either ” cry ” or ” cries ” depending on the value of helpCount. The operator works by testing the boolean expression before the ?. If helpCount == 1 is true, then everthing to the right of the assignment operator is replaced by ” cry ”. If false, then everything to the right of the assignment operator is replaced by ” cries ”.
se1020/eventhandling.txt · Last modified: 2009/06/03 11:22 (external edit)
 

This website is not owned or managed by the Milwaukee School of Engineering.

© 2003-2010 Dr. Christopher C. Taylor, et. al. • Office: L-343 • Phone: 277-7339 • npǝ˙ǝosɯ@ɹolʎɐʇ • -> RSS <-