第11章 事件处理
对于图形用户界面的程序来说,事件处理是十分重要的。要想实现用户界面,必须掌握Java 事件处理的基本方法。本章将讲解 Java AWT 事件模型的工作机制, 从中可以看到如何捕获用户界面组件和输入设备产生的事件。另外,本章还介绍如何以更加结构化的方式处理动作(actions) 事件。
11.1 事件处理基础
程序员对相关的特定事件编写代码, 并将这些代码放置在过程中,通常人们将它们称为事件过程( event procedure) 。 每个 Visual Basic 的GUI 组件都响应一个固定的事件集,不可能改变 Visual Basic 组件响应的事件集。
另一方面,如果使用像原始的 C 这样的语言进行事件驱动的程序设计, 那就需要编写代码来不断地检查事件队列, 以便査询操作环境报告的内容(通常这些代码被放置在包含很多switch语句的循环体中)。 它的好处在于响应的事件不受限制, 而不像 Visual Basic 这样的语言,将事件队列对程序员隐藏起来。
Java 程序设计环境折中了 Visual Basic 与原始 C 的事件处理方式, 因此, 它既有着强大的功能, 又具有一定的复杂性。在 AWT 所知的事件范围内, 完全可以控制事件从事件源( event source) 例如, 按钮或滚动条, 到事件监听器(event listener) 的传递过程, 并将任何对象指派给事件监听器。不过事实上,应该选择一个能够便于响应事件的对象。这种事件委托模型(event delegation model ) 与 Visual Basic 那种预定义监听器模型比较起来更加灵活。
事件源有一些向其注册事件监听器的方法。当某个事件源产生事件时, 事件源会向为事件注册的所有事件监听器对象发送一个通告。
像 Java 这样的面向对象语言, 都将事件的相关信息封装在一个事件对象( event object)中。在 Java 中,所有的事件对象都最终派生于 java.util.EventObject 类。
不同的事件源可以产生不同类别的事件。 例如, 按钮可以发送一个 ActionEvent 对象,而窗口可以发送 WindowEvent 对象。
11.1.1 实例: 处理按钮点击事件
可以通过在按钮构造器中指定一个标签字符串、 一个图标或两项都指定来创建一个按钮。
将按钮添加到面板中需要调用 add 方法:
接下来需要增加让面板监听这些按钮的代码。这需要一个实现了 ActionListener 接口的类。 如前所述, 应该包含一个 actionPerformed 方法
注释: 在按钮示例中, 使用的 ActionListener 接口并不仅限于按钮点击事件。 它可以应用于很多情况:
•当采用鼠标双击的方式选择了列表框中的一个选项时;
•当选择一个菜单项时;
•当在文本域中按回车键时;
•对于一个 Timer 组件来说, 当达到指定的时间间隔时。
在本章和下一章中, 读者将会看到更加详细的内容。
在所有这些情况下, 使用 ActionListener 接口的方式都是一样的:actionPerformed 方法 (ActionListener 中的唯一方法)将接收一个 ActionEvent 类型的对象作为参数。这个事件对象包含了事件发生时的相关信息。
当按钮被点击时, 希望将面板的背景颜色设置为指定的颜色。 这个颜色存储在监听器中:
然后, 为每种颜色构造一个对象, 并将这些对象设置为按钮监听器。
这里还有一个需要考虑的问题。CobrAction 对象不能访问 buttonpanel 变量。 可以采用两种方式解决这个问题。一个是将面板存储在 ColorAction 对象中,并在 ColorAction 的构造器中设置它;另一个是将 ColorAction 作为 ButtonFrame 类的内部类,这样一来,它的方法就自动地拥有访问外部面板的权限了(有关内部类的详细介绍请参看第 6 章)。
这里使用第二种方法。下面说明一下如何将 ColorAction 类放置在 ButtonFrame 类内。
下面仔细地研究 actionPerformed 方法。在 ColorAction类中没有 buttonPanel 域,但在外部 ButtonFrame 类中却有。
这种情形经常会遇到。事件监听器对象通常需要执行一些对其他对象可能产生影响的操作。可以策略性地将监听器类放置在需要修改状态的那个类中。
【API】javax.swing.JButton 1.2 :
- 构造一个按钮。标签可以是常规的文本, 从 Java SE 1.3 开始, 也可以是 HTML。例如,“”。
【API】java.awt.Container 1.0 :
- 将组件 c 添加到这个容器中。
11.1.2 简洁地指定监视器
现在考虑这样一种情况: 有多个相互关联的动作, 如上一节中的彩色按钮。在这种情况下, 可以实现一个辅助方法:
需要说明, lambda 表达式指示参数变量 backgroundColor。
然后只需要调用:
注释: 在较老的代码中, 通常可能会看到使用匿名类:
当然, 已经不再需要这种繁琐的代码。使用 lambda 表达式更简单, 也更简洁。
注释:有些程序员不习惯使用内部类或 lambda 表达式, 而更喜欢创建实现了 ActionListener接口的事件源容器。 然后这个容器再设置自身作为监听器。 如下所示:
yellowButton_ addActionListener(this);
blueButton.addActionListener(this);
redButton.addActionListener(this);
现在这 3 个按钮不再有单独的监听器。 它们共享一个监听器对象, 具体来讲就是框架 (frame )。 因此, actionPerformed 方法必须明确点击了哪个按钮。
我们并不建议采用这种策略
注释:lambda表达式出现之前,还可以采用一种机制来指定事件监听器,其事件处理器包含一个方法调用。例如,假设一个按钮监听器需要执行以下调用:
EventHandler 类可以用下面的调用创建这样一个监听器:
这种方法现在已经成为历史。 利用 lambda 表达式, 可以更容易地使用以下调用:
EventHandler机制的效率也不高,而且比较容易出错。它使用反射来调用方法。 出于这个原因,EventHandler.create 调用的第二个参数必须属于一个公有类。否 则,反射机制就无法确定和调用目标方法。
【API】java.awt.event.ActionEvent 1.1:
- 返回与这个动作事件关联的命令字符串。 如果这个动作事件源自一个按钮, 命令字符串就等于按钮标签, 除非已经用 setActionCommand 方法改变了命令字符串。
【API】java.beans.EventHandler 1.4:
- 构造实现给定接 n 的一个代理类的对象。接口的指定方法或所有方法会在目标对象上 执行给定的动作。
这个动作可以是一个方法名, 或者是目标的一个属性。 如果这是一个属性, 则执行它的设置方法。例如, 动作 “text” 会转换为一个 setText 方法调用。
事件属性由一个或多个由点号(.)分隔的属性名组成。第一个属性从监听器方法的参数读取,第二个属性由得到的对象读取, 依此类推。最终结果会成为动作的参数。例如, 属性 “source.text” 会转换为 getSource 和 getText 方法调用。
【API】java.util.EventQbject 1.1:
- 返回发生这个事件的对象的一个引用。
11.1.3 实例:改变观感
在默认情况下, Swing 程序使用 Metal 观感,可以采用两种方式改变观感。 第一种方式是在 Java 安装的子目录 jre/lib 下有一个文件 swing.properties。在这个文件中,将属性 swing.defaultlaf 设置为所希望的观感类名。
注意, Metal 和 Nimbus 观感位于 javax.swing 包中。其他的观感包位于 com.sun.java 包中, 并且不是在每个 Java 实现中都提供。现在, 鉴于版权的原因, Windows 和 Macintosh 的观感包只与 Windows 和 Macintosh 版本的 Java 运行时环境一起发布。
提示:由于属性文件中以 # 字符开始的行被忽略, 所以, 可以在 swing.properties 文件中提供几种观感选择, 并通过增删 # 字符来切换选择:
#swing.defaultl af=javax.swing.pi af.metal .Metal LookAnd Feel
swi ng.default af=com.sun. java.swing.plaf.motif.MotifLookAndFeel
#swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel
采用这种方式开启观感时必须重新启动程序。Swing 程序只在启动时读取一次 swing.properties 文件。
第二种方式是动态地改变观感。这需要调用静态的 UIManager.setLookAndFeel 方法,并提供所想要的观感类名, 然后再调用静态方法 SwingUtilities.updateComponentTreeUI 来刷新全部的组件集。这里需要向这个方法提供一个组件,并由此找到其他的所有组件。
下面是一个示例,它显示了如何用程序切换至 Motif 观感:
为了列举安装的所有观感实现, 可以调用
然后采用下列方式得到每一种观感的名字和类名
我们遵循前一节的建议, 使用辅助方法 makeButton 和匿名内部类指定按钮动作, 即切换观感。
注释: 在本书之前的版本中, 我们使用了一个内部匿名类来定义这个监听器。 那时, 我们要特别注意向 SwingUtilities.updateComponentTreeUI 传递 PlafFrame.this ( 而不是内部类的 this 引用):
有了 lambda 表达式之后, 就不存在这个问题了。在一个 lambda 表达式中, this 就指示外围的对象。
【API】javax.swing.UIManager 1.2 :
- 获得一个用于描述已安装的观感实现的对象数组。
- 利用给定的类名设置当前的观感。例如,javax.swing.plaf.metal.MetalLookAndFeel
【API】javax.swing.UIManager.LookAndFeellnfo 1.2 :
- 返回观感的显示名称。
- 返回观感实现类的名称。
11.1.4 适配器类
当程序用户试图关闭一个框架窗口时, JFrame 对象就是 WindowEvent 的事件源。 如果希望捕获这个事件, 就必须有一个合适的监听器对象, 并将它添加到框架的窗口监听器列表中。
窗口监听器必须是实现 WindowListener 接口的类的一个对象。在 WindowListener 接口中包含 7 个方法。当发生窗口事件时, 框架将调用这些方法响应 7 个不同的事件。从它们的名字就可以得知其作用, 唯一的例外是在 Windows 下, 通常将 iconified (图标化)称为minimized (最小化)。 下面是完整的 WindowListener 接口:
注释:为了能够查看窗口是否被最大化,需要安装WindowStateListener并覆盖windowStateChanged 方法。
当然, 可以这样定义实现这个接口的类: 在 windowClosing 方法中增加一个对 System.exit(0) 的调用,其他 6个方法不做任何事情
书写 6 个没有任何操作的方法代码显然是一种乏味的工作。鉴于简化的目的, 每个含有多个方法的 AWT 监听器接口都配有一个适配器(adapter) 类,这个类实现了接口中的所有方法, 但每个方法没有做任何事情。这意味着适配器类自动地满足了 Java 实现相关监听器接口的技术需求。可以通过扩展适配器类来指定对某些事件的响应动作, 而不必实现接口中的每个方法(ActionListener 这样的接口只有一个方法,因此没必要提供适配器类。)
瞀告: 如果在扩展适配器类时将方法名拼写错了, 编译器不会捕获到这个错误。 例如,如果在 WindowAdapter 类中定义一个 windowIsClosing 方法, 就会得到一个包含 8 个方法的类,. 并且 windowClosing 方法没有做任何事情。 可以使用 @Override 注角 (在第 5 章已经介绍过)避免这种错误。
创建一个扩展于 WindowAdapter 的监听器类是一个很好的改进, 但是还可以继续改进。事实上, 没有必要为 listener 对象命名。
不要就此止步!我们可以将监听器类定义为框架的匿名内部类。
这段代码具有下列作用:
- 定义了一个扩展于 WindowAdapter 类的无名类。
- 将 windowClosing 方法添加到匿名类中(与前面一样,这个方法将退出程序。)
- 从 WindowAdapter 继承 6 个没有做任何事情的方法。
- 创建这个类的一个对象, 这个对象没有名字。
- 将这个对象传递给 addWindowListener 方法。
这里重申一次, 匿名内部类的语法需要人们适应一段时间, 但得到的是更加简练的代码。
注释: 如今, 可能有人会把 WindowListener 接口中什么也不做的方法实现为默认方法。不过, Swing 早在有默认方法很多年之前就已经问世了。(笔记注解:等于说可以用Java8的接口默认方法特性简化适配器?)
【API】java.awt.event.WindowListener 1.1:
- 窗口打开后调用这个方法。
- 在用户发出窗口管理器命令关闭窗口时调用这个方法。需要注意的是, 仅当调用 hide 或 dispose 方法后窗口才能够关闭。
- 窗口关闭后调用这个方法。
- 窗口图标化后调用这个方法。
- 窗口非图标化后调用这个方法。
- 激活窗口后调用这个方法。只有框架或对话框可以被激活。通常, 窗口管理器会对活动窗口进行修饰, 比如, 髙亮度标题栏。
- 窗口变为未激活状态后调用这个方法。
【API】java.awt.event.WindowStateListener 1.4 :
- 窗口被最大化、 图标化或恢复为正常大小时调用这个方法。
【API】java.awt.event.WlndowEvent 1.1:
- 返回窗口状态改变事件中窗口的新、旧状态。返回的整型数值是下列数值之一:
Frame.NORMAL
Fraae.ICONIFIED
Fraae.MAXIMIZED_HORIZ
Frame.HAXIMIZED_VERT
Frame.MAXIMIZED_BOTH
11.2 动作
通常, 激活一个命令可以有多种方式。用户可以通过菜单、击键或工具栏上的按钮选择特定的功能。在 AWT 事件模型中实现这些非常容易:将所有事件连接到同一个监听器上。
Swing 包提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是Action 接口。一个动作是一个封装下列内容的对象:
- 命令的说明(一个文本字符串和一个可选图标);
- 执行命令所需要的参数(例如,在列举的例子中请求改变的颜色)。
Action 接口包含下列方法:
第一个方法是 ActionListener 接口中很熟悉的一个: 实际上,Action 接口扩展于 ActionListener 接口,因此,可以在任何需要 ActionListener 对象的地方使用 Action 对象。
接下来的两个方法允许启用或禁用这个动作,并检査这个动作当前是否启用。当一个连接到菜单或工具栏上的动作被禁用时, 这个选项就会变成灰色。
putValue 和 getvalue 方法允许存储和检索动作对象中的任意名 / 值。有两个重要的预定义字符串: Action.NAME 和 Action.SMALL_ICON,用于将动作的名字和图标存储到一个动作对象中:
action.putValue(Action.NAME , “Blue”);
action.putValue(Action.SMALL_ICON , new ImageIcon(“blue-ball.gif”));
如果动作对象添加到菜单或工具栏上,它的名称和图标就会被自动地提取出来, 并显示在菜单项或工具栏项中。SHORT_DESCRIPTION 值变成了工具提示。
Action 接口的最后两个方法能够让其他对象在动作对象的属性发生变化时得到通告, 尤其是菜单或工具栏触发的动作。
需要注意, Action 是一个接口, 而不是一个类。实现这个接口的所有类都必须实现刚才讨论的 7 个方法。庆幸的是,有一个类实现了这个接口除 actionPerformed 方法之外的所有方法,它就是 AbstractAction。这个类存储了所有名 / 值对, 并管理着属性变更监听器。我们可以直接扩展 AbstractAction 类,并在扩展类中实现 actionPerformed 方法。
下面构造一个用于执行改变颜色命令的动作对象。首先存储这个命令的名称、 图标和需要的颜色。将颜色存储在 AsbstractAction 类提供的名 / 值对表中。下面是 ColorAction 类的代码。构造器设置名 / 值对, 而 actionPerformed 方法执行改变颜色的动作。
想要将这个动作对象添加到击键中, 以便让用户敲击键盘命令来执行这项动作。为了将动作与击键关联起来, 首先需要生成 Keystroke 类对象。这是一个很有用的类, 它封装了对键的说明。要想生成一个 Keystroke 对象,不要调用构造器, 而是调用 Keystroke 类中的静态 getKeyStroke 方法
为了能够理解下一个步骤,需要知道的概念。用户界面中可以包含许多按钮、 菜单、 滚动栏以及其他的组件。当用户敲击键盘时, 这个动作会被发送给拥有焦点的组件。通常具有焦点的组件可以明显地察觉到(但并不总是这样,) 例如, 在 Java 观感中,具有焦点的按钮在按钮文本周围有一个细的矩形边框。用户可以使用 TAB 键在组件之间移动焦点。当按下 SPACE 键时, 就点击了拥有焦点的按钮。还有一些键执行一些其他的动作,例如, 按下箭头键可以移动滚动条。
Swing 设计者给出了一种很便捷的解决方案。每个 JComponent 有三个输入映射(imput map), 每一个映射的 Keystroke 对象都与动作关联。三个输人映射对应
着三个不同的条件(请参看表 11-2 )。
警告: JDK 文档提倡使用动作名作为动作键。 我们并不认为这是一个好建议。 在按钮和菜单项上显示的动作名, UI 设计者可以随心所欲地进行更改, 也可以将其翻译成多种语言。使用这种不牢靠的字符串作为查询键不是一种好的选择。 建议将动作名与显示的名字分开。
获得或设置这个动作的 enabled 属性。
将名 / 值对放置在动作对象内。
参数: key 用动作对象存储性能的名字。它可以是一个字符串, 但预定义了几个名字, 其含义参看表 11-1。
value 与名字关联的对象。
返回被存储的名 / 值对的值。
根据一个便于人们阅读的说明创建一个按键 (由空格分隔的字符串序列)。这个说明以0 个或多个修饰符 shift control Ctrl meta alt altGraph 开始, 以由 typed 和单个字符构成的字符串(例如:“ typeda”)或者一个可选的事件说明符(pressed 默认, 或 released)紧跟一个键码结束。 以 VK_前缀开始的键码应该对应一个 KeyEvent 常量, 例如,“ INSERT” 对应 KeyEvent.VK_INSERT。
1.3
返回关联动作映射键(可以是任意的对象)和动作对象的映射。
1.3
获得将按键映射到动作键的输入映射。
参数: flag 触发动作的键盘焦点条件。具体的值请参看表 11-2。
br> 对象作为参数, 调用 getX 和 getY 方法可以获得鼠标被按下时鼠标指针所在的 x 和 y 坐标。 要想区分单击、 双击和三击(!,) 需要使用 getClickCount方法。
col>
col>
br> Toolkit tk = Toolkit.getDefaultToolkit();
br> Image img = tk.getlmage(“dynamite.gif”);
br> Cursor dynamiteCursor = tk.createCustomCijrsor(inig, new Point (10, 10) , “dynamite stick”) ;
br> createCustomCursor 的第一个参数指向光标图像。 第二个参数给出了光标的“ 热点”偏移。 第三个参数是一个描述光标的字符串。 这个字符串可以用于访问性支持, 例如,可以将光标形式读给视力受损或没有在屏幕前面的人。
br> 返回事件扩展的或“ 按下”(down) 的修饰符。使用下面的掩码值检测返回值:
br> BUTTON1_DOWN_MASK
br> BUTTON2_DOWN_MASK
br> BUTTON3_DOWN_MASK
br> SHIFT_DOWN_MASK
br> CTRL_DOWN_MASK
br> ALT_DOWN_MASK
br> ALT_GRAPH_DOWN_MASK
br> META_DOWN_MASK
br> 返回用给定标志集描述的扩展或“ 按下” (down) 的修饰符字符串, 例如“Shift+Button1” 。
br> 创建一个新的定制光标对象。
br> 参数: image 光标活动时显示的图像
br> hotSpot 光标热点(箭头的顶点或十字中心)
br> name 光标的描述, 用来支持特殊的访问环境
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/675.html