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:
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.
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.
One of the most common type of events is an action event.
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:
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.
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"); } } }
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:
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.
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:
Notice:
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.
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
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:
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.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 ”.