Listeners
Listeners are how anything gets done in Swing. Clicking on stuff causes Events. Events are sort of like little messages that get sent around inside your program. If you want, you can watch all these messages and try to filter out the ones you want. That would be a colossal waste of time, on top of being really, really inefficient. There are better ways to do things. Namely, Event Listeners.
Event Listeners are functions you define that will be called when Events happen. The JFC/Swing core tells each Component - like JButtons and JMenus - when an Event they would be interested in occurs. This is how JButtons know to make themselves look "clicked". You can also ask a JButton to tell you when it gets clicked by registering an Event Listener with it. If you were to write Java code with Emacs, there would be 3 steps involved in registering an Event Listener with a JButton:
1 - Define the listener:
There are different kinds of Events, so there are different kinds of Listeners. JButton generates an ActionEvent when it is clicked, so we create a class that implements the ActionListener interface...
class AnActionListener implements ActionListener{
public void actionPerformed(ActionEvent actionEvent){
System.out.println("I was selected.");
}
}
2 - Create an instance of the listener
Ok, this is pretty simple...
ActionListener actionListener = new AnActionListener();
3 - Register the listener with a component
Start out by pretending you already have a JButton and you want that listener function we wrote in step 1 to get called when J. Random User clicks on it. You do this by registering it with the JButton. Essentially you ask the JButton to add it to the list of functions it calls when it decides to generates an ActionEvent, like so...
JButton button = new JButton();
... // other code
button.addActionListener(actionListener);
Visual Age to the Rescue!
Most programs have lots of buttons. And Menus. And List boxes. And Other Stuff (tm). Writing Event Listeners for all of these would be tedious and error-prone. Debugging Event Listeners isn't much fun either. That's why we have Visual IDE's (Integrated Development Environments). In VAJ, you can draw some buttons and other components, then do some pointy-clicky stuff and VAJ will create and manage all those Event Listeners for you. Most of the time you don't even have to write any code! Ok, I'm getting a little too excited, that's it for Listeners.
Figure 1. A simple GUI
Figure 1's GUI comprises a panel containing three buttons and a label. When you press a button, the GUI writes a message to the label indicating which button you pressed.
To display the panel, I embedded it within a frame. However, for the purposes of today's Java Q&A, I'll simply focus on the panel code. (Please see Resources for the full source code.)
Implementation
Following the discussion in the original Java Q&A, you can implement the buttons' listeners in two ways. First, the panel can implement the ActionListener interface directly, or, second, you can create three listener classes whose instances listen to the buttons directly. Let's look at the first choice in which the panel implements one large listener.
Wire the buttons directly to the panel
The following class shows how to wire the buttons directly to the panel:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* The ActionListenerGUI is an example of a simple GUI that directly implements
* the ActionListener Interface.
* @author Tony Sintes
*/
public class ActionListenerPanel extends JPanel implements ActionListener {
public ActionListenerPanel() {
setup();
}
private void setup() {
setLayout( new BorderLayout() );
JButton button1 = new JButton( BUTTON_1 );
button1.setActionCommand( BUTTON_1_ACTION );
button1.addActionListener( this );
JButton button2 = new JButton( BUTTON_2 );
button2.setActionCommand( BUTTON_2_ACTION );
button2.addActionListener( this );
JButton button3 = new JButton( BUTTON_3 );
button3.setActionCommand( BUTTON_3_ACTION );
button3.addActionListener( this );
JPanel buttons = new JPanel();
buttons.add( button1 );
buttons.add( button2 );
buttons.add( button3 );
add( display, BorderLayout.NORTH );
add( buttons, BorderLayout.SOUTH );
}
public void actionPerformed( ActionEvent actionEvent ) {
String actionName = actionEvent.getActionCommand().trim();
if( actionName.equals( BUTTON_1_ACTION ) ) {
display.setText( BUTTON_1 );
}
else if( actionName.equals( BUTTON_2_ACTION ) ) {
display.setText( BUTTON_2 );
}
else if( actionName.equals( BUTTON_3_ACTION ) ) {
display.setText( BUTTON_3 );
}
}
private JLabel display = new JLabel("Press a button");
private static String BUTTON_1 = "Button 1";
private static String BUTTON_2 = "Button 2";
private static String BUTTON_3 = "Button 3";
private static String BUTTON_1_ACTION = "B1";
private static String BUTTON_2_ACTION = "B2";
private static String BUTTON_3_ACTION = "B3";
}
In the code above, notice the setup() and actionPerformed() methods. In setup(), I create three buttons. Each button creation takes the following form:
JButton buttonN = new JButton( BUTTON_N );
buttonN.setActionCommand( BUTTON_N_ACTION );
buttonN.addActionListener( this );
Each time I add a button, I give it an action command and set the panel directly as the listener, a design choice whose outcome becomes apparent in actionPerformed()'s implementation:
public void actionPerformed( ActionEvent actionEvent ) {
String actionName = actionEvent.getActionCommand().trim();
if( actionName.equals( BUTTON_1_ACTION ) ) {
display.setText( BUTTON_1 );
}
else if( actionName.equals( BUTTON_2_ACTION ) ) {
display.setText( BUTTON_2 );
}
else if( actionName.equals( BUTTON_3_ACTION ) ) {
display.setText( BUTTON_3 );
}
}
Every time I call actionPerformed(), it must first switch through the action command names to determine which button I pressed, and then take the proper action.
Next, let's look at an approach that completely removes the if/else mapping.
Wire the buttons to their own listeners
The following class wires each button directly to its own listener:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* ModularGUI presents an alternative to having the main GUI implement the
* ActionListener interface directly.
* @author Tony Sintes
*/
public class ModularPanel extends JPanel {
public ModularPanel() {
setup();
}
private void setup() {
setLayout( new BorderLayout() );
JButton button1 = new JButton( BUTTON_1 );
button1.addActionListener( new Button1Action () );
JButton button2 = new JButton( BUTTON_2 );
button2.addActionListener( new Button2Action() );
JButton button3 = new JButton( BUTTON_3 );
button3.addActionListener( new Button3Action() );
JPanel buttons = new JPanel();
buttons.add( button1 );
buttons.add( button2 );
buttons.add( button3 );
add( display, BorderLayout.NORTH );
add( buttons, BorderLayout.SOUTH );
}
private JLabel display = new JLabel("Press a button");
private static String BUTTON_1 = "Button 1";
private static String BUTTON_2 = "Button 2";
private static String BUTTON_3 = "Button 3";
private class Button1Action implements ActionListener {
public void actionPerformed( ActionEvent actionEvent ) {
display.setText( BUTTON_1 );
}
}
private class Button2Action implements ActionListener {
public void actionPerformed( ActionEvent actionEvent ) {
display.setText( BUTTON_2 );
}
}
private class Button3Action implements ActionListener {
public void actionPerformed( ActionEvent actionEvent ) {
display.setText( BUTTON_3 );
}
}
}
Again, notice the setup() and actionPerformed() methods. In setup(), note the button creation takes a slightly different pattern:
JButton buttonN = new JButton( BUTTON_N );
buttonN.addActionListener( new ButtonNAction () );
Now, whenever I create a button, I instantiate a new listener and directly associate it with the button. I need not assign an action command name. You can easily see that design choice's outcome in the actionPerformed() implementation. The panel doesn't implement the method any longer!
Instead of the panel directly implementing a large actionPerformed() method, each button should be associated in a one-to-one relationship with an object customized for listening to the specific button. Here are the ActionListener implementations:
private class Button1Action implements ActionListener {
public void actionPerformed( ActionEvent actionEvent ) {
display.setText( BUTTON_1 );
}
}
private class Button2Action implements ActionListener {
public void actionPerformed( ActionEvent actionEvent ) {
display.setText( BUTTON_2 );
}
}
private class Button3Action implements ActionListener {
public void actionPerformed( ActionEvent actionEvent ) {
display.setText( BUTTON_3 );
}
}
Notice how I can completely remove the if/else mess present in the first implementation. Each case now becomes its own object. When I wire the buttons this way, the proper actionPerformed() method will get called directly when I press the button -- no need to switch through action command names. A clean design does the mapping work for you.
Here, I chose to implement the listeners as inner classes. Depending upon your application and tastes, you can also implement listeners as anonymous classes, as standalone classes, or as a combination of inner/anonymous classes and standalone classes. For example, an ActionListener could delegate the actual response to some other object. Such an approach helps keep the response code independent of the GUI, thus letting you reuse your actions elsewhere and easily change your GUI's behavior.
How the approaches differ
The two approaches differ in several ways.
Object responsibility
Whether or not you like the second approach depends on how you view responsibilities.
The first approach assumes that the panel's responsibilities include:
Building the display
Wiring the buttons to the listeners
Mapping the action events to the proper response
Performing the proper response (or perhaps delegating the response to another object or to a method)
The second approach assumes that the panel's responsibilities include:
Building the display
Wiring the buttons to the listeners
The second approach responds to the action events out of the panel and places that responsibility into a separate object. It also does not assume the panel maps a button's action to the action to perform. Instead, that responsibility lies in the button itself. Instead of a large if/else switch, the button can call the object that knows how to perform the action directly.
Yes, nothing stops you from delegating to a second object from within the if/else block, but why insist on the if/else switch block if you don't need it? Why embed that responsibility in the GUI when objects can do it for you?
Good OO design tells us that an object should have only one responsibility and that behavior should be grouped. With that in mind, the second approach better embodies good OO design: the GUI builds the GUI (which includes hooking up the listeners), while the individual ActionListeners perform one action. In the first approach, the GUI does everything.
I like most how the second approach encourages the Command design pattern. The Command pattern handily turns actions into objects. Once your actions are objects, you can benefit from the advantages objects give, such as reuse and plugability.
Maintenance
From a maintenance perspective, I think that the first approach demands more work. That is, to add a new button, you must create a new button, associate a new action command name with it, then add another switch to the monolithic actionPerformed() method (keeping the action name straight).
In contrast, the second approach lets you simply create a new button, define an object that responds to the button, and wire the two together. Since we're programming in objects, I find it easier to read and understand small objects. I don't like wading through pages of if/else statements to find and debug actions.
One reader suggested putting the action into a method to increase readability. While that might help, it forces me to ask what we are trying to accomplish. Certainly we're not trying to perform procedural programming. Placing the action into a proc--- method smacks of procedural programming. The action doesn't belong inside of a method; it belongs inside an object.
For me, if we're using Java so we can program in an object-oriented way, we should take full advantage of objects, which includes taking the intellectual leap and turning our actions into objects in their own right.
By using objects, I hope to write software that is flexible, easy to understand, and reusable -- goals the second approach accomplishes, while the first approach does not.
A final word on efficiency
This article arose as a response to the argument that creating so many objects somehow proves inefficient. However, at worst you pay the small memory overhead from a few extra listener objects. From a performance point of view, eliminating the long if/else switch can improve performance -- you can avoid all those checks.
From a developer's point of view, I think the object-based approach represents an effective tradeoff given how it will help future maintenance and extension. As a user, I'd rather give up a little memory for a better performing application. Finally, in large applications, I have seen dramatic responsiveness improvements by moving to the object-based approach.
本文地址:http://com.8s8s.com/it/it13768.htm