分享
 
 
 

something about Listeners

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

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.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有