Interacting with users

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:

  • misusing inheritance and combinatorial explosion of classes
  • poor separation between application model, logic and GUI
  • cumbersome syntax
  • single object could not handle events from multiple components

The Observer pattern

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, ChangeListner 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 )

A simple event example

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();
	}
}

Initially, middle label shows 0This 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);

When button is clicked, counter changesImmediately 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:

  • JButton class was not artificially inherited in order to provide an action on the event
  • Button does not actually know what needs to happen when it is clicked
  • Button click handler does not know which button he is listening to

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:

Window dimensions switch

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();
	}
}

Exploring events

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 {
    
    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 {
 
    
    public void mouseClicked(MouseEvent e);
 
    
    public void mousePressed(MouseEvent e);
 
    
    public void mouseReleased(MouseEvent e);
 
    
    public void mouseEntered(MouseEvent e);
 
    
    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:

  • Layout/Operation related event listeners: AncestorListener, ChangeListener, ContainerListener, FocusListener, HierarchyListener. Unless you are extending the GUI by implementing your own Containers and Components from scratch, you can disregard these events. FocusListener is the only one from this group that is (rarely) used in day-to-day Swing development - notification about losing focus is sent when the user stops working inside a component (by clicking on Tab key, pressing a hotkey for some other component, or just clicking outside of the screen). So this event can be used to save text after editing, for example. However, be aware that focus is also lost when user minimises the application window.
  • Input device event listeners: KeyListener, MouseListener, MouseMotionListener, MouseWheelListener; These are used mostly when developing custom components for advanced GUI interaction - e.g. in game development. Swing provides a fine abstraction of low-level input methods for standard GUI components that are used in business applications, so there is rarely any need to act directly on mouse clicks or keyboard presses. However, if you want to draw an image and then enable user to interact with parts of it, you will most likely need to implement Mouse and Keyboard listeners.
  • Action events: these high-level events encapsulate complexity of clicks, moves, focuses and other GUI and screen related technicalities, and provide you with a fine abstraction like “button pressed” or “text modified”. For AbstractButton subclasses (JButton, JToggleButton, JCheckBox, JRadioButton, JMenuItem, JMenu) this event is called ‘ActionEvent’. Other components provide different actions (for carret movements, for example).
  • Model events: these provide an even further abstraction for some components - model events are not fired on visual or GUI changes, but on the changes of the underlying data model. For example, text fields have an underlying Document, and you can listen to the ‘logical’ changes in that document rather then listening to ‘physical’ clicks and carret moves. Complex components like tables and trees have underlying models with appropriate change events and listeners (use table.getModel() to get to the TableModel, or tree.getModel() to get to the TreeModel).

Which event should you use?

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:

  • key pressed java.awt.event.KeyEvent[KEY_PRESSED,keyCode=85,keyText=U,keyChar=’u’,keyLocation=KEY_LOCATION_STANDARD] on javax.swing.JTextField[…]
  • key typed java.awt.event.KeyEvent[KEY_TYPED,keyCode=0,keyText=Unknown keyCode: 0×0,keyChar=’u’,keyLocation=KEY_LOCATION_UNKNOWN] on javax.swing.JTextField[…]
  • inserted [javax.swing.text.GapContent$InsertUndo@12d03f9 hasBeenDone: true alive: true]
  • key released java.awt.event.KeyEvent [KEY_RELEASED,keyCode=85,keyText=U,keyChar=’u’,keyLocation=KEY_LOCATION_STANDARD] on javax.swing.JTextField […]

These are important things you should try:

  • When you edit text in the field a combination of Key and Document events is fired. Document events let you know what’s happening with the text (parts are inserted or removed) and Key events let you know what’s happening with the keyboard (keys get 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.
  • Press Tab to move to the ‘Click’ button - and you will see a focus lost event. Press Tab again to go back to the text field, you will see a focus gained event. Now click on the ‘Click’ button using mouse - focus is lost again.
  • Start editing text, and then minimise window - focus will be lost again. Maximise the window, and focus is gained
  • Edit text and press enter - action event occurs.

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).

Working with action maps

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.

Long actions

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.

  • Block the calling GUI object (button that caused action to perform) before starting a new thread; this way users will not be able to click on the button twice and start two new threads.
  • If you want to update GUI information from the background thread, you should do it using the Event dispatch thread.
  • When the processing is finished, display a message and unblock the calling object.

See swing_threading for a detailed discussion of best practices and pitfalls of multithreaded event processing.

Download code

  • Source code for chapter 3 examples: ch3.zip

External Links


Previous: Building Screen Elements | Hands on Swing Table of Contents | Other Swing Books

 

Comments? Corrections? Contact us or Login to edit pages directly (registration is free and takes less than displaying a JLabel)
  handson/interacting_with_users.txt · Last modified: 2006/09/15 05:33 by 213.52.177.99 (gojko)
 
Recent changes | RSS changes | Table of contents | News Archive | Terms And Conditions | Register | The Quest For Software++

Sedo - Buy and Sell Domain Names and Websites project info: swingwiki.org Statistics for project swingwiki.org etracker� web controlling instead of log file analysis