In the first versions of Java AWT, the preferred way to interact with users was to override methods that received events in the components, so you would typically handle a mouse click by overriding mouseDown method of a button. Though this type of event handling is still taught at some universities, it has been deprecated since Java 1.1 and will not be discussed further here. Event delegation model was introduced as a systematic and flexible way to address shortcomings of the previous model, namely:
Event delegation model is based on the Observer pattern, much more flexible and allowing proper separation of application model and GUI. In short, Observer pattern has two participants – Observer is is interested in changes of an Observable object, and may perform some action when the Observable object changes. The Observable object does not need to know what the Observer does, or how it performs the action – Observers subscribe to receive events, and Observable objects just notify all registered Observers when an event occurs. By insulating Observers with an interface that different classes can implement, this model effectively separates GUI objects and actions from application model and action code. GUI components just know that their observers will obey a certain interface, and do not need to know the concrete classes of observers. Observers typically contain code to execute application logic, separate from GUI code.
Though java.util.Observable and java.util.Observer seem to be defined exactly for the purpose of implementing Observer pattern, Swing components do not follow that convention. Instead, a simple naming convention is used. A noun is typically used to describe an event type – examples are Action, Change or Key. More complex event type names contain two words, like TableModel or MouseWheel. Swing Observer interfaces are called Listeners, and their names typically begin with event type, followed by Listener suffix – examples are: ActionListener, ChangeListener or KeyListener. Typical Observable object will have one or more methods with names beginning with add, followed by the event name – examples are addActionListener, addChangeListener and addKeyListener. Method for removing listeners is similar – but the prefix is remove. Those methods are used to register Observers, and there are no other naming conventions for Observable objects. Event data passed from Observable objects to Observers is usually an instance of class named like the event type, with Event suffix – examples are ActionEvent, ChangeEvent and KeyEvent. All event classes and listener interfaces are either in javax.awt.event or javax.swing.event package.
| Event type | Event object | Listener Interface | Registration method |
|---|---|---|---|
| Key | KeyEvent | KeyListener | addKeyListener(KeyListener) |
| Action | ActionEvent | ActionListener | addActionListener(ActionListener) |
| TableModel | TableModelEvent | TableModelListener | addTableModelListener(TableModelListener ) |
This example is not exactly written according to highest standards, but it will clearly demonstrate the principles of event delegation. We’ll create a dialog that increments a click counter each time the the user clicks on a button. First, we do the event handler: an implementation of ActionListener class that has an associated JLabel and tracks number of clicks. On each click, the label is changed to display the new click count:
package com.neuri.handsonswing.ch3; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JLabel; class ButtonClickHandler implements ActionListener{ private JLabel label; private int clicks=0; ButtonClickHandler (JLabel label){ this.label=label; } public void actionPerformed(ActionEvent e) { label.setText(String.valueOf(++clicks)); } }
Next, we initialise a dialog with two labels and one button, and subscribe a button click handler to the button:
package com.neuri.handsonswing.ch3; import java.awt.*; import javax.swing.*; public class SimpleEventDemo { void start(){ JFrame fr=new JFrame("Simple Event Demo"); fr.setDefaultCloseOperation(fr.EXIT_ON_CLOSE); fr.setSize(300,100); Container content=new JPanel(new BorderLayout()); fr.setContentPane(content); JLabel clickLabel=new JLabel("0"); JLabel topLabel=new JLabel("Number of clicks:"); JButton b=new JButton("Click me"); content.add(BorderLayout.NORTH,topLabel); content.add(BorderLayout.CENTER,clickLabel); content.add(BorderLayout.SOUTH,b); ButtonClickHandler bch=new ButtonClickHandler(clickLabel); b.addActionListener(bch); fr.show(); } public static void main(String[] args) { new SimpleEventDemo().start(); } }
This example initialises three screen components - two labels and a button. Middle label simply displays a zero, indicating that the button has not yet been pressed. ButtonClickHandler is an Observer for the button, and subscribes in order to receive notifications when the button is clicked:
ButtonClickHandler bch=new ButtonClickHandler(clickLabel); b.addActionListener(bch);
Immediately after the click, button notifies all subscribed listeners about the event by calling their actionPerformed method; ButtonClickHandler will simply update internal click counter and display it’s current value.
public void actionPerformed(ActionEvent e) { label.setText(String.valueOf(++clicks)); }
You should note several ideas in this example:
You can easily handle more than one button with the same action object:
package com.neuri.handsonswing.ch3; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class MultiEventDemo { void start(){ JFrame fr=new JFrame("Multi Event Demo"); fr.setDefaultCloseOperation(fr.EXIT_ON_CLOSE); fr.setSize(300,100); Container content=new JPanel(new BorderLayout()); fr.setContentPane(content); JLabel topLabel=new JLabel("Number of clicks:"); JButton b=new JButton("Click me"); JButton b2=new JButton("Click me 2"); JLabel clickLabel=new JLabel("0"); content.add(BorderLayout.NORTH,topLabel); content.add(BorderLayout.CENTER,clickLabel); JPanel bp=new JPanel(); bp.add(b); bp.add(b2); content.add(BorderLayout.SOUTH,bp); ButtonClickHandler bch=new ButtonClickHandler(clickLabel); b.addActionListener(bch); b2.addActionListener(bch); fr.show(); } public static void main(String[] args) { new MultiEventDemo().start(); } }
You can easily perform more than one action on any event by subscribing different listeners - in addition to just incrementing counter, let’s switch window dimensions:
package com.neuri.handsonswing.ch3; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; public class MultiSubscriberDemo { JFrame fr=new JFrame("Multi Subscriber Demo"); class TransposeWindow implements ActionListener{ public void actionPerformed(ActionEvent e) { fr.setSize(fr.getHeight(), fr.getWidth()); } } void start(){ fr.setDefaultCloseOperation(fr.EXIT_ON_CLOSE); fr.setSize(200,100); Container content=new JPanel(new BorderLayout()); fr.setContentPane(content); JLabel topLabel=new JLabel("Number of clicks:"); JButton b=new JButton("Click me"); JLabel clickLabel=new JLabel("0"); content.add(BorderLayout.NORTH,topLabel); content.add(BorderLayout.CENTER,clickLabel); content.add(BorderLayout.SOUTH,b); ButtonClickHandler bch=new ButtonClickHandler(clickLabel); b.addActionListener(bch); b.addActionListener(new TransposeWindow ()); fr.show(); } public static void main(String[] args) { new MultiSubscriberDemo().start(); } }
Since all event handler registration methods begin with add, it’s easy to spot events supported by any component - just look for add methods in the code helper window of your favourite IDE. Event listener interfaces typically have one callback method, though more complex events can have several methods which enable you to filter several event types. A typical example of one-method listener is MouseWheelListener:
public interface MouseWheelListener extends EventListener { /** * Invoked when the mouse wheel is rotated. * @see MouseWheelEvent */ public void mouseWheelMoved(MouseWheelEvent e); }
Using this interface is fairly straightforward - there is only one method which gets called every time the user scrolls a mouse wheel.
A typical example of a listener with more than one method is MouseListener
public interface MouseListener extends EventListener { /** * Invoked when the mouse button has been clicked (pressed * and released) on a component. */ public void mouseClicked(MouseEvent e); /** * Invoked when a mouse button has been pressed on a component. */ public void mousePressed(MouseEvent e); /** * Invoked when a mouse button has been released on a component. */ public void mouseReleased(MouseEvent e); /** * Invoked when the mouse enters a component. */ public void mouseEntered(MouseEvent e); /** * Invoked when the mouse exits a component. */ public void mouseExited(MouseEvent e); }
If you are interested in only one of those cases, then think about using Adapter classes. Adapters are default implementations of complex listener interfaces, which just ignore all received events. You can inherit an Adapter and override just the method that you are interested in, without having to write 5 other empty methods. This makes the code more compact; but there is a limitation - classes can inherit only from one superclass, so subclassing an Adapter is only possible when your action object does not inherit from anything else.
Typically, you will notice methods to register several types of listeners:
table.getModel() to get to the TableModel, or tree.getModel() to get to the TreeModel).
In short, it’s always cleanest to use model events, because they hide both the complexity of user interactions and allow you to display the same model in several components, modify it from various places, and easily act on all the changes. If the model is not available, try component-action events - especially look for ActionEvent first. Component domain events hide the complexity of user interactions - Users might click on a menu item using left mouse button, navigating to the item and pressing space or enter, or clicking on a hot-key; you can act on all those events just by listening to ActionEvent. This makes the code much easier to write and read, and less error-prone, since you do not have to think about double-clicks, acting on mouse button being pressed or released, or whether user dragged the mouse pointer while the button was pressed.
Here is a simple example which just prints out that an event occurred, and tracks several types of events. Notice how the event listeners were defined in the registration procedure - this method of using anonymous classes saves some typing, and is common practice for Listeners, so get used to reading it.
package com.neuri.handsonswing.ch3; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class TextFieldEventDemo { private JLabel topLabel = new JLabel("Edit me:"); private JTextField jtf = new JTextField(10); void start() { JFrame fr = new JFrame("Text Field Event Demo"); fr.setDefaultCloseOperation(fr.EXIT_ON_CLOSE); fr.setSize(300, 100); Container content = new JPanel(new BorderLayout()); fr.setContentPane(content); content.add(BorderLayout.NORTH, topLabel); content.add(BorderLayout.CENTER, jtf); content.add(BorderLayout.SOUTH, new JButton("click")); jtf.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { System.out.println("changed " + e.toString()); } public void insertUpdate(DocumentEvent e) { System.out.println("inserted " + e.toString()); } public void removeUpdate(DocumentEvent e) { System.out.println("removed " + e.toString()); } }); jtf.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("action " + e.toString()); } }); jtf.addFocusListener(new FocusListener() { public void focusLost(FocusEvent e) { System.out.println("focus lost " + e.toString()); } public void focusGained(FocusEvent e) { System.out.println("focus gained " + e.toString()); } }); jtf.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { System.out.println("key pressed " + e.toString()); } public void keyReleased(KeyEvent e) { System.out.println("key released " + e.toString()); } public void keyTyped(KeyEvent e) { System.out.println("key typed " + e.toString()); } }); fr.show(); } public static void main(String[] args) { new TextFieldEventDemo().start(); } }
Start the application and play with it for a bit. You will see messages on the console about events. This happens when you type in a simple letter:
These are important things you should try:
pressed and released - if they are printable then they also get typed). Click on Shift and hold it for a few moments to see the difference - only pressed and released get called. There were no keys typed and the document was not modified. Now, copy some text into the clipboard, go back to the text field, and click on Ctrl-V (or whatever key combination is used to paste text from clipboard). You will see that the document was modified, without keys being inserted. Even better, if you are using Linux, go to the text field and click on middle mouse button (or both buttons if you are using a 2 button mouse) - this pastes the text from clipboard without any keyboard events.If you want to wait until the user enters some data in the text field and act on his command, you will most likely use the same ActionListener on the text field and “Save” button. If you want to act on all changes in the text, you will most likely use Document events (for example, to show search results while the user is typing in search keywords).
From Java 1.3, there is another way to process ‘Actions’ - you can define abstract actions, and bind ActionListeners to those actions. Then, using InputMaps, define which key stokes cause those abstract actions:
package com.neuri.handsonswing.ch3; import java.awt.*; import java.awt.event.*; import java.io.IOException; import javax.swing.*; public class ActionDemo { private JLabel topLabel = new JLabel("press F1 for help:"); private JDialog helpDialog; private JTextField jtf = new JTextField(10); void start() throws IOException { JFrame fr = new JFrame("Text Field Event Demo"); fr.setDefaultCloseOperation(fr.EXIT_ON_CLOSE); fr.setSize(300, 100); JPanel content = new JPanel(new BorderLayout()); fr.setContentPane(content); content.add(BorderLayout.NORTH, topLabel); content.add(BorderLayout.CENTER, jtf); content.add(BorderLayout.SOUTH, new JButton("click")); fr.show(); helpDialog = new JDialog(fr, "Help"); helpDialog.getContentPane().add( new JEditorPane("http://www.google.com")); helpDialog.setSize(300, 300); helpDialog.setVisible(false); KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0); content.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( ks, "HELP"); content.getActionMap().put("HELP", new AbstractAction() { public void actionPerformed(ActionEvent evt) { helpDialog.setVisible(true); } }); } public static void main(String[] args) throws IOException { new ActionDemo().start(); } }
Run the application and hit F1 - you should see a help dialog with Google page loading.
ActionMaps and EventMaps can be chained, so you can forward unhandled events to parent handlers. Also, you can put events into different input maps (example above uses map JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT), so this way of handling actions is very useful for global actions such as loading help windows.
This model of action handling is specific for JComponent classes and subclasses, so AWT classes and JFrame/JDialog do not have them. If you want to define a global action for the frame, add it to action/input map of the main content panel.
If the action you are performing on events is not instant, but takes a while, it’s a good practice to do the processing in the background, and not block the GUI.
See swing_threading for a detailed discussion of best practices and pitfalls of multithreaded event processing.
Previous: Building Screen Elements | Hands on Swing Table of Contents | Other Swing Books