8.1 事件处理基础
事件源有一些向其注册事件监听器的方法。当某个事件源产生事件的时候,事件源会向为事件注册的所有事件监听器对象发送一个通告。
在Java中,所有的事件对象都最终派生于java.util.EventObject类。
AWT事件处理机制的概要:
1)监听器对象是一个实现了特定监听接口(listener interface)的类的实例。
2)事件源是一个能够注册监听器对象并发送事件对象的对象。
3)当事件发生时,事件源将事件对象传递给所有注册的监听器。
4)监听器对象将利用事件对象中的信息决定如何对事件做出响应。
8.1.1 实例:处理按钮点击事件
8.1.2 建议使用内部类
8.1.3 将组件变成事件监听器
8.1.4 实例:改变观感
在默认情况下,Swing程序使用Metal观感,可以采用两种方式改变观感。第一种方式是在Java安装的子目录jre/lib下有一个文件swing.properties。在这个文件中,将swing.defaultaf设置为所希望的观感类名。(采用这种方式开启观感时必须重新启动程序。Swing程序只在启动时读取一次swing.properties文件);第二种方式是动态地改变观感。这需要调用静态的UIManager.setLookAndFeel方法,并提供所想要的观感类名,然后再调用静态方法SwingUtilities.updateComponentTreeUI来刷新全部的组件集。
8.1.5 实例:捕获窗口事件
适配器类
出于简化的目的,每个含有多个方法的AWT监听器接口都配有一个适配器(adapter)类,这个类实现了接口中的所有方法,但每个方法没有做任何事情。可以通过扩展适配器类来指定对某些事件的响应动作,而不必实现接口中的每个方法。
8.2 AWT事件继承层次
事件对象封装了事件源与监听器彼此通信的事件信息。在必要的时候,可以对传递给监听器对象的事件对象进行分析。
8.3 AWT的语义事件和低级事件
AWT将事件分为低级(low-level)事件和语义(semantic)事件。语义事件是指表达用户动作的事件,例如,点击按钮;因此,ActionEvent是一种语义事件。低级事件是指形成那些事件的事件。在点击按钮时,包含了按下鼠标、连续移动鼠标、抬起鼠标(只有鼠标在按钮区中抬起才引发)事件。或者在用户利用TAB键选择按钮,并利用空格键激活它时,发送的敲击键盘事件。同样,调节滚动条时一种语义事件,但拖动鼠标是低级事件。
事件处理总结
事件源是用户界面组件、窗口和菜单。操作系统会将用户的动作通知给相关的事件源,例如移动鼠标和敲击键盘。事件源在事件对象(event object)中描述了事件属性。事件源还保存着一组监听器(listener),这是事件发生时需要调用的对象。事件发生后,事件源调用监听器(listener interface)的相应方法,以便将时间的相关信息传递给不同的监听器。事件源通过将相应的事件对象传递给监听器中的方法来实现这一点。监听器分析时间对象,以便获取与时间相关的详细信息。
8.4 低级事件类型
8.4.1 键盘事件
Java明确区分字符和虚拟键码(virtual key code)虚拟键码用前缀VK_表示,例如,VK_A或VK_SHIFT。虚拟键码与键盘上的键一一对应。例如,VK_A表示被标记为A的键。虚拟键码没有单独的小写键,即键盘没有单独的小写键。
注意:虚拟键码涉及“扫描码”,这是在按下一个物理键或释放一个物理键时,键盘向计算机发送的编码。
注意:并不是所有的敲击键盘都会产生keyTyped调用。只有那些产生Unicode字符的敲击才能够在keyTyped方法中捕获。可以使用keyPressed方法检查光标键和其他命令键。
这里有一个技巧:通常,面板不接受任何键盘事件。因此,可以调用setFocusable方法对默认情形进行覆盖。
8.4.2 鼠标事件
当用户点击鼠标按钮时,将会调用三个监听器方法:鼠标第一次被按下时调用mousePressed;鼠标被释放时调用mouseReleased;最后调用mouseClicked。
提示:可以在jre/lib/images/cursors目录中找到光标图像。在文件cursors.properties中定义了光标“热点”。热点是指示光标引起动作的点。例如,如果光标变成放大镜形状,热点就是该镜的中心。
注意:可以利用Toolkit类中的createCustomCursor方法自定义光标类型:
Toolkit tk=Toolkit.getDefaultToolkit();
Image img=tk.getImage("dynamite.gif");
Cursor dynamiteCursor=tk.createCustomCursor()img,new Point(10,10),"dynamite stick");
createCustomCursor的第一个三四指向光标图像。第二个参数给出了光标的“热点”偏移。第三个参数是一个描述光标的字符串。这个字符可以用于访问性支持,例如,可以将光标形式读给视力受损或没有在屏幕面前的人。
注意:只有鼠标在一个组件内部停留才会调用mouseMoved方法。然而,即使鼠标拖动到组件外面,mouseDragged方法也会被调用。
8.4.3 焦点事件
用鼠标可以指向屏幕上的任何一个对象。但是在使用键盘输入时,敲击键盘必须定位于一个特定的屏幕对象。窗口管理器(window manager,例如,Windows 或X Windows)直接将所有的击键定位于活动窗口(active window)。通常,活动窗口用高亮度显示的标题栏进行区分。在任何时刻,只有一个窗口可以是活动的。
有些组件,例如,标签和面板,在默认情况下无法得到焦点,这是因为它们主要用于装饰或分组。如果希望编写一个程序,能够根据用户的击键操作在面板上绘制图形,就需要对默认处理进行覆盖。在JDK1.4中,可以通过简单地调用下面这个方法达到目的。
panel.setFocusable(true);
在JDK1.4中,当焦点转移的时候,可以找到“对等物”组件或窗口。对等物是指在组件或窗口失去焦点时获得焦点的组件或窗口。相反地,当组件或窗口获得焦点时,对等物是刚刚失去焦点的那个组件或窗口。FocusEvent类中的getOppositeComponent方法将报告对等组件,WindowEvent类中的getOppositeWindow方法将报告对等的窗口。
注意:不应该再requestFocus或requestFocusInWindow返回true时就认定组件拥有焦点,而应该等待FOCUS_GAINED事件的发送。
注意:有些程序员觉得FOCUS_LOST事件有些混乱,并试图在focusLost处理器中通过请求获得焦点来阻止其他组件。然而,在这个时候,焦点已经失去了。如果必须在一个特定的组件中捕获焦点,就需要在KeyboardFocusManager中安装“禁止改变监听器”,并禁用focusOwner属性。
8.5 动作
Swing提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是Action接口。一个动作是一个封装下列内容的对象:
1)命令的说明(一个文本字符串和一个可选图标)。
2)执行命令所需要的参数(例如,在列举的例子中请求改变的颜色)。
如果动作对象被添加到菜单或工具栏上,它的名称和图标就会被自动地提取出来,并显示在菜单项或工具栏项中。SHORT_DESCRIPTION值变成了工具提示。
为了将动作与基建关联起来,首先需要生成KeyStroke类对象。这是一个很有用的类,它封装了对键的说明。要想生成一个KeyStroke对象,不要调用构造器,而是调用KeyStroke类中的静态getKeyStroke方法,并指定虚拟键码和标记。
KeyStroke ctrlBKey=KeyStroke.getKeyStroke(KeyEvent.VK_B,InputEvent.CTRL_MASK);
除此之外,还可以用字符串对击键进行描述:
KeyStroke ctrlBKey=KeyStroke.getKeyStroke("ctrl B");
每个JComponent有三个输入映射(input map),每一个映射的KeyStroke对象都与动作关联。三个输入映射对应着三个不同的条件。
击键处理将按照下列顺序检查这些映射:
1)检查具有输入焦点组件的WHEN_FOCUSED映射。如果这个击键存在,将执行对应的动作。如果动作已启用,则停止处理。
2)从具有输入焦点的组件开始,检查其父组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT映射。一旦找到击键对应的映射,就执行对应的动作。如果动作已启用,将停止处理。
3)查看具有输入焦点的窗口中所有可视的和启用的组件,这个击键被注册到WHEN_IN_FOCUSED_WINDOW映射中。给这些组件(按照击键注册的顺序)一个执行对应动作的机会。一旦第一个启用的动作被执行,就停止处理。如果一个击键在多个WHEN_IN_FOCUSED_WINDOW映射中出现,这部分处理就可能会出现问题。
表8-4 输入映射条件
标 志 激 活 动 作
WHEN_FOCUSED 当该组件拥有键盘焦点时
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT 当该组件包含了拥有键盘焦点的组件时
WHEN_IN_FOCUSED_WINDOW 当该组件被包含在一个拥有键盘焦点组件的窗口中时
8.6 多点传送 2010-09-27 308页
所有的AWT事件源都支持监听器的多点传送(multicast)模型。这意味着同一个事件可以发送给多个监听器对象。如果存在多方可能对一个事件感兴趣,那么就需要使用多点传送。将多个监听器添加到一个事件源中就可以使得所有注册的监听器都能够有机会对这些事件作出响应。
警告:根据JDK文档,“API不能保证一个给定事件源注册的一组监听器传送事件的次序”。因此,不要编写依赖传送次序的程序逻辑。
8.7 实现事件源
无论何时定义事件源,都需要具有下面三个要素:
1)事件类型(event type)。可以定义自己的事件类。
2)事件监听接口(event listener interface)。还可以定义自己的接口。
3)增加或删除监听器的的方法。
事件管理师一项常见的任务,Swing设计者提供了一种很方便的类EventListenerList,从而可以轻而易举地实现增加、删除监听器和激活事件的方法。当多个线程试图在同一时刻增加、删除或分发事件时有可能出现的问题,这个类给予了比较细致的考虑。
事件源有一些向其注册事件监听器的方法。当某个事件源产生事件的时候,事件源会向为事件注册的所有事件监听器对象发送一个通告。
在Java中,所有的事件对象都最终派生于java.util.EventObject类。
AWT事件处理机制的概要:
1)监听器对象是一个实现了特定监听接口(listener interface)的类的实例。
2)事件源是一个能够注册监听器对象并发送事件对象的对象。
3)当事件发生时,事件源将事件对象传递给所有注册的监听器。
4)监听器对象将利用事件对象中的信息决定如何对事件做出响应。
8.1.1 实例:处理按钮点击事件
8.1.2 建议使用内部类
8.1.3 将组件变成事件监听器
8.1.4 实例:改变观感
在默认情况下,Swing程序使用Metal观感,可以采用两种方式改变观感。第一种方式是在Java安装的子目录jre/lib下有一个文件swing.properties。在这个文件中,将swing.defaultaf设置为所希望的观感类名。(采用这种方式开启观感时必须重新启动程序。Swing程序只在启动时读取一次swing.properties文件);第二种方式是动态地改变观感。这需要调用静态的UIManager.setLookAndFeel方法,并提供所想要的观感类名,然后再调用静态方法SwingUtilities.updateComponentTreeUI来刷新全部的组件集。
8.1.5 实例:捕获窗口事件
适配器类
出于简化的目的,每个含有多个方法的AWT监听器接口都配有一个适配器(adapter)类,这个类实现了接口中的所有方法,但每个方法没有做任何事情。可以通过扩展适配器类来指定对某些事件的响应动作,而不必实现接口中的每个方法。
8.2 AWT事件继承层次
事件对象封装了事件源与监听器彼此通信的事件信息。在必要的时候,可以对传递给监听器对象的事件对象进行分析。
8.3 AWT的语义事件和低级事件
AWT将事件分为低级(low-level)事件和语义(semantic)事件。语义事件是指表达用户动作的事件,例如,点击按钮;因此,ActionEvent是一种语义事件。低级事件是指形成那些事件的事件。在点击按钮时,包含了按下鼠标、连续移动鼠标、抬起鼠标(只有鼠标在按钮区中抬起才引发)事件。或者在用户利用TAB键选择按钮,并利用空格键激活它时,发送的敲击键盘事件。同样,调节滚动条时一种语义事件,但拖动鼠标是低级事件。
事件处理总结
事件源是用户界面组件、窗口和菜单。操作系统会将用户的动作通知给相关的事件源,例如移动鼠标和敲击键盘。事件源在事件对象(event object)中描述了事件属性。事件源还保存着一组监听器(listener),这是事件发生时需要调用的对象。事件发生后,事件源调用监听器(listener interface)的相应方法,以便将时间的相关信息传递给不同的监听器。事件源通过将相应的事件对象传递给监听器中的方法来实现这一点。监听器分析时间对象,以便获取与时间相关的详细信息。
8.4 低级事件类型
8.4.1 键盘事件
Java明确区分字符和虚拟键码(virtual key code)虚拟键码用前缀VK_表示,例如,VK_A或VK_SHIFT。虚拟键码与键盘上的键一一对应。例如,VK_A表示被标记为A的键。虚拟键码没有单独的小写键,即键盘没有单独的小写键。
注意:虚拟键码涉及“扫描码”,这是在按下一个物理键或释放一个物理键时,键盘向计算机发送的编码。
注意:并不是所有的敲击键盘都会产生keyTyped调用。只有那些产生Unicode字符的敲击才能够在keyTyped方法中捕获。可以使用keyPressed方法检查光标键和其他命令键。
这里有一个技巧:通常,面板不接受任何键盘事件。因此,可以调用setFocusable方法对默认情形进行覆盖。
8.4.2 鼠标事件
当用户点击鼠标按钮时,将会调用三个监听器方法:鼠标第一次被按下时调用mousePressed;鼠标被释放时调用mouseReleased;最后调用mouseClicked。
提示:可以在jre/lib/images/cursors目录中找到光标图像。在文件cursors.properties中定义了光标“热点”。热点是指示光标引起动作的点。例如,如果光标变成放大镜形状,热点就是该镜的中心。
注意:可以利用Toolkit类中的createCustomCursor方法自定义光标类型:
Toolkit tk=Toolkit.getDefaultToolkit();
Image img=tk.getImage("dynamite.gif");
Cursor dynamiteCursor=tk.createCustomCursor()img,new Point(10,10),"dynamite stick");
createCustomCursor的第一个三四指向光标图像。第二个参数给出了光标的“热点”偏移。第三个参数是一个描述光标的字符串。这个字符可以用于访问性支持,例如,可以将光标形式读给视力受损或没有在屏幕面前的人。
注意:只有鼠标在一个组件内部停留才会调用mouseMoved方法。然而,即使鼠标拖动到组件外面,mouseDragged方法也会被调用。
8.4.3 焦点事件
用鼠标可以指向屏幕上的任何一个对象。但是在使用键盘输入时,敲击键盘必须定位于一个特定的屏幕对象。窗口管理器(window manager,例如,Windows 或X Windows)直接将所有的击键定位于活动窗口(active window)。通常,活动窗口用高亮度显示的标题栏进行区分。在任何时刻,只有一个窗口可以是活动的。
有些组件,例如,标签和面板,在默认情况下无法得到焦点,这是因为它们主要用于装饰或分组。如果希望编写一个程序,能够根据用户的击键操作在面板上绘制图形,就需要对默认处理进行覆盖。在JDK1.4中,可以通过简单地调用下面这个方法达到目的。
panel.setFocusable(true);
在JDK1.4中,当焦点转移的时候,可以找到“对等物”组件或窗口。对等物是指在组件或窗口失去焦点时获得焦点的组件或窗口。相反地,当组件或窗口获得焦点时,对等物是刚刚失去焦点的那个组件或窗口。FocusEvent类中的getOppositeComponent方法将报告对等组件,WindowEvent类中的getOppositeWindow方法将报告对等的窗口。
注意:不应该再requestFocus或requestFocusInWindow返回true时就认定组件拥有焦点,而应该等待FOCUS_GAINED事件的发送。
注意:有些程序员觉得FOCUS_LOST事件有些混乱,并试图在focusLost处理器中通过请求获得焦点来阻止其他组件。然而,在这个时候,焦点已经失去了。如果必须在一个特定的组件中捕获焦点,就需要在KeyboardFocusManager中安装“禁止改变监听器”,并禁用focusOwner属性。
8.5 动作
Swing提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是Action接口。一个动作是一个封装下列内容的对象:
1)命令的说明(一个文本字符串和一个可选图标)。
2)执行命令所需要的参数(例如,在列举的例子中请求改变的颜色)。
如果动作对象被添加到菜单或工具栏上,它的名称和图标就会被自动地提取出来,并显示在菜单项或工具栏项中。SHORT_DESCRIPTION值变成了工具提示。
为了将动作与基建关联起来,首先需要生成KeyStroke类对象。这是一个很有用的类,它封装了对键的说明。要想生成一个KeyStroke对象,不要调用构造器,而是调用KeyStroke类中的静态getKeyStroke方法,并指定虚拟键码和标记。
KeyStroke ctrlBKey=KeyStroke.getKeyStroke(KeyEvent.VK_B,InputEvent.CTRL_MASK);
除此之外,还可以用字符串对击键进行描述:
KeyStroke ctrlBKey=KeyStroke.getKeyStroke("ctrl B");
每个JComponent有三个输入映射(input map),每一个映射的KeyStroke对象都与动作关联。三个输入映射对应着三个不同的条件。
击键处理将按照下列顺序检查这些映射:
1)检查具有输入焦点组件的WHEN_FOCUSED映射。如果这个击键存在,将执行对应的动作。如果动作已启用,则停止处理。
2)从具有输入焦点的组件开始,检查其父组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT映射。一旦找到击键对应的映射,就执行对应的动作。如果动作已启用,将停止处理。
3)查看具有输入焦点的窗口中所有可视的和启用的组件,这个击键被注册到WHEN_IN_FOCUSED_WINDOW映射中。给这些组件(按照击键注册的顺序)一个执行对应动作的机会。一旦第一个启用的动作被执行,就停止处理。如果一个击键在多个WHEN_IN_FOCUSED_WINDOW映射中出现,这部分处理就可能会出现问题。
表8-4 输入映射条件
标 志 激 活 动 作
WHEN_FOCUSED 当该组件拥有键盘焦点时
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT 当该组件包含了拥有键盘焦点的组件时
WHEN_IN_FOCUSED_WINDOW 当该组件被包含在一个拥有键盘焦点组件的窗口中时
8.6 多点传送 2010-09-27 308页
所有的AWT事件源都支持监听器的多点传送(multicast)模型。这意味着同一个事件可以发送给多个监听器对象。如果存在多方可能对一个事件感兴趣,那么就需要使用多点传送。将多个监听器添加到一个事件源中就可以使得所有注册的监听器都能够有机会对这些事件作出响应。
警告:根据JDK文档,“API不能保证一个给定事件源注册的一组监听器传送事件的次序”。因此,不要编写依赖传送次序的程序逻辑。
8.7 实现事件源
无论何时定义事件源,都需要具有下面三个要素:
1)事件类型(event type)。可以定义自己的事件类。
2)事件监听接口(event listener interface)。还可以定义自己的接口。
3)增加或删除监听器的的方法。
事件管理师一项常见的任务,Swing设计者提供了一种很方便的类EventListenerList,从而可以轻而易举地实现增加、删除监听器和激活事件的方法。当多个线程试图在同一时刻增加、删除或分发事件时有可能出现的问题,这个类给予了比较细致的考虑。