2.2.3 管理监听器列表
如果我们正在创建我们自己的组件并且希望这些组件触发事件,我们需要维护一个要通知的监听器列表。如果监听器列表是用于AWT事件的,我们可以使用AWTEventMulticaster类用于列表管理。对于Swing库而言,如果事件并不是一个预定义的AWT事件类型,我们需要自己管理监听器列表。通过使用javax.swing.event包中的EventListenerList类,我们不再需要手动管理监听器列表,也无需担心线程安全。而且如果我们需要获取监听器列表,我们可以通过public EventLIstener[] getListener(Class listenerType)来请求Component,或者是类似于JButton的getActionListeners()方法的类型特定方法。这使得我们可以由一个内部管理列表中移除监听器,从而有助于垃圾回收。
AWTEventMulticaster类
无论我们是否意识到,AWTEventMulticaster类被AWT组件用来管理事件监听器列表。这个类实现了所有的AWT事件监听器(ActionListener, AdjustmentListener, ComponentListener, ContainerListener, FocusListener, HierarchyBoundsListener, HierarchyListener, InputMethodListener, ItemListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, TextListener, WindowFocusListener, WindowListener以及WindowStatListener)。无论何时我们调用组件的方法来添加或是移除一个监听器时,AWTEventMulticaster都会被用来作为支持。
如果我们希望创建我们自己的组件并且管理用于AWT事件/监听器对的监听器列表,我们可以使用AWTEventMulticaster。作为一个示例,我们来看一下如何创建一个通用组件,当按键在组件内部按下时,这个组件会生成一个ActionEvent对象。这个组件使用KeyEvent的public static String getKeyText(int keyCode)方法来将按键代码转换相应的文本字符串,并且将这个文本字符串作为ActionEvent的动作命令回传。因为这个组件是作为ActionListener观察者的源,他需要一对添加/移除方法来处理监听器的注册。这也就是AWTEventMulticaster类的用处所在,因为他会管理由我们的监听器列表变量中监听器的添加或移除:
private ActionListener actionListenerList = null; public void addActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.add( actionListenerList, actionListener); } public void removeActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.remove( actionListenerList, actionListener); }
类定义的其余部分描述了如何处理内部事件。为了向ActionListener发送击键需要注册一个内部的KeyListener。另外,组件必须能够获得输入焦点;否则,所有的击键都会到达其他的组件。完整的类定义如列表2-4所示。用于监听器通知的代码行以粗体显示。这一行通知所有的已注册的监听器。
/** * */ package swingstudy.ch02; import java.awt.AWTEventMulticaster; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JComponent; /** * @author lenovo * */ public class KeyTextComponent extends JComponent{ private ActionListener actionListenerList = null; public KeyTextComponent() { setBackground(Color.CYAN); KeyListener internalKeyListener = new KeyAdapter() { public void keyPressed(KeyEvent event) { if(actionListenerList != null) { int keyCode = event.getKeyCode(); String keyText = event.getKeyText(keyCode); ActionEvent actionEvent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, keyText); actionListenerList.actionPerformed(actionEvent); } } }; MouseListener internalMouseListener = new MouseAdapter() { public void mousePressed(MouseEvent event) { requestFocusInWindow(); } }; addKeyListener(internalKeyListener); addMouseListener(internalMouseListener); } public void addActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.add(actionListenerList, actionListener); } public void removeActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.remove(actionListenerList, actionListener); } public boolean isFocusable() { return true; } }
图2-5显示所有的组件。图中上部分是组件,而下底部则是一个文本输入框。为了显示按下键的文本字符串,向更新文本框的KeyTextComponent注册了一个ActionListener。
示例源码如列表2-5所示。
/** * */ package swingstudy.ch02; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JTextField; /** * @author lenovo * */ public class KeyTextTester { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Key Text Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); KeyTextComponent keyTextComponent = new KeyTextComponent(); final JTextField textField = new JTextField(); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { String keyText = event.getActionCommand(); textField.setText(keyText); } }; keyTextComponent.addActionListener(actionListener); frame.add(keyTextComponent, BorderLayout.CENTER); frame.add(textField, BorderLayout.SOUTH); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
EventListenerList类
尽管AWTEventMulticaster类很容易使用,然而他却并不能用于管理自定义的事件监听器列表或是javax.swing.event中的Swing事件器。我们可以创建一个这个类的自定义扩展用于处理我们需要管理的每一种类型的事件监听器列表,或者我们可以将列表存储在一个如Vector或是LinkedList的数据结构中。尽管使用Vector或是LinkedList可以工作得很好,当我们使用这种方法时,我们需要考虑同步问题。如果我们没有正确的编写列表管理,监听器通知也许会发生错误的监听器集合。
为了简化这种情况,Swing组件库包含了一个特殊的事件监听吕地类,EventListenerList。这个类的一个实例可以管理一个组件的所有不同的事件监听器。为了演示这个类的用法,我们来看一下如何使用EventListenerList替换AWTEventMulticaster来重写前面的例子。注意,在这个特定例子中,使用AWTEventMulticaster类实际上是一种更为简单的解决方法。然而,想像一个类似的情况下,在这种情况下事件监听器并不是一个预定义的AWT事件监听器或者是我们需要维护多个监听器列表。
添加或是移除监听器类似于在前面的例子中AWTEventMulticaster所用的技术。我们需要创建一个合适的变量类型-这次是EventListenerList-同时定义添加与移除监听器方法。这两种方法之间的主要区别在于初始的EventListenerList并不为null,而另一个初始时则是null。首先必须创建一个到空的EventListenerList的引用。这避免了在后面多次检测null列表变量的需要。添加与移除监听器的方法也有一些不同。因为EventListenerList可以管理任意类型的监听器列表,当我们添加或是移除监听器时,我们必须提供起作用的监听器类类型。
EventListenerList actionListenerList = new EventListenerList(); public void addActionListener(ActionListener actionListener) { actionListenerList.add(ActionListener.class, actionListener); } public void removeActionListener(ActionListener actionListener) { actionListenerList.remove(ActionListener.class, actionListener); }
这只留下了要处理的监听器通知。在这个类中并不存在通用方法来通知有事件发生的特定类型的监听器,所以我们必须创建我们自己的代码。fireActionPerformed(actionEvent)的调用将会替代前面例子中的actionListenerList.actionPerformed(actionEvent)。这行代码会以数据形式由列表中获取一份一个特定类型的所有监听器的拷贝(以线程安全方式)。然后我们需要在这个列表中循环并通知合适的监听器。
protected void fireActionPerformed(ActionEvent actionEvent) { EventListener listenerList[] = actionListenerList.getListeners(ActionListener.class); for (int i=0, n=listenerList.length; i
/** * */ package swingstudy.ch02; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.EventListener; import javax.swing.JComponent; import javax.swing.event.EventListenerList; /** * @author lenovo * */ public class KeyTextComponent2 extends JComponent{ private EventListenerList actionListenerList = new EventListenerList(); public KeyTextComponent2() { setBackground(Color.CYAN); KeyListener internalKeyListener = new KeyAdapter() { public void keyPressed(KeyEvent event) { if(actionListenerList != null) { int keyCode = event.getKeyCode(); String keyText = event.getKeyText(keyCode); ActionEvent actionEvent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, keyText); fireActionPerformed(actionEvent); } } }; MouseListener internalMouseListener = new MouseAdapter() { public void mousePressed(MouseEvent event) { requestFocusInWindow(); } }; addKeyListener(internalKeyListener); addMouseListener(internalMouseListener); } public void addActionListener(ActionListener actionListener) { actionListenerList.add(ActionListener.class, actionListener); } public void removeActionListener(ActionListener actionListener) { actionListenerList.remove(ActionListener.class, actionListener); } public void fireActionPerformed(ActionEvent event) { EventListener[] listenerList = actionListenerList.getListeners(ActionListener.class); for(int i=0, n=listenerList.length; ipublic boolean isFocusable() { return true; } }