Java 中文官方教程 2022 版(二十四)(2)https://developer.aliyun.com/article/1486794
课程:编写事件监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/index.html
示例索引
本课程为您提供了编写事件监听器的详细信息。您可能不需要阅读本节。您处理事件的首要信息来源应该是相关组件的操作指南部分。每个组件的部分都展示了在实现组件时最常用的事件处理代码。例如,如何使用复选框展示了如何使用项目监听器处理复选框上的鼠标点击。
一些简单的事件处理示例
本节中的程序演示了事件和事件处理。
编写事件监听器的一般信息
本节提供了处理所有类型事件的有用信息。其中一个主题包括使用适配器和内部类来实现事件处理程序的信息。
Swing 组件支持的监听器
这是唯一可以查找到哪些 Swing 组件可以触发哪些类型事件的地方。您可能希望将本节加为书签,以便轻松找到其快速参考表。
为常见处理事件实现监听器
本节提供了编写每种常见类型事件监听器的详细信息和示例。
监听器 API 表
本节展示了一个快速参考表,显示了每个监听器、其适配器类(如果有)以及其方法。
解决常见事件处理问题
如果您遇到与事件处理相关的难以调试的问题,您可能会在这里找到解决方案。
问题和练习
尝试这些问题和练习,测试您在本课程中学到的知识。
如果您有兴趣使用 JavaFX 创建 GUI,请参阅处理 JavaFX 事件。
事件监听器简介
原文:
docs.oracle.com/javase/tutorial/uiswing/events/intro.html
如果你已经阅读了任何组件的操作指南页面,你可能已经了解了事件监听器的基础知识。
让我们看一个可能的最简单的事件处理示例。它被称为蜂鸣器,当你点击按钮时会发出蜂鸣声。
点击“启动”按钮以使用Java™ Web Start运行蜂鸣器(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
您可以在Beeper.java
中找到整个程序。以下是实现按钮事件处理的代码:
public class Beeper ... implements ActionListener { ... *//where initialization occurs:* button.addActionListener(this); ... public void actionPerformed(ActionEvent e) { *...//Make a beep sound...* } }
Beeper
类实现了ActionListener
接口,其中包含一个方法:actionPerformed
。由于Beeper
实现了ActionListener
,一个Beeper
对象可以注册为按钮触发的动作事件的监听器。一旦Beeper
使用Button
的addActionListener
方法注册,每次点击按钮时都会调用Beeper
的actionPerformed
方法。
更复杂的示例
事件模型在前面的示例中展示得很简单,但非常强大和灵活。任意数量的事件监听器对象可以监听来自任意数量的事件源对象的各种事件。例如,一个程序可以为每个事件源创建一个监听器。或者一个程序可以对来自所有源的所有事件拥有一个单一的监听器。一个程序甚至可以对来自单个事件源的单一类型的事件拥有多个监听器。
多个监听器可以注册以接收特定类型的事件来自特定来源。此外,同一个监听器可以监听来自不同对象的通知。
每个事件都由一个对象表示,该对象提供有关事件的信息并标识事件源。事件源通常是组件或模型,但其他类型的对象也可以是事件源。
每当你想要检测特定组件的事件时,请首先查看该组件的操作指南部分。组件操作指南部分的列表在这里。操作指南部分提供了处理你可能关心的事件的示例。例如,在如何使用颜色选择器中,你会找到一个编写更改监听器以跟踪颜色选择器中颜色更改的示例。
以下示例演示了事件监听器可以注册在多个对象上,并且相同的事件可以发送给多个监听器。该示例包含两个事件源(JButton
实例)和两个事件监听器。其中一个事件监听器(名为MultiListener
的类的实例)监听来自两个按钮的事件。当它接收到事件时,它将事件的“动作命令”(设置为按钮标签上的文本)添加到顶部文本区域。第二个事件监听器(名为Eavesdropper
的类的实例)仅监听一个按钮上的事件。当它接收到事件时,它将动作命令添加到底部文本区域。
试试这个:
- 点击启动按钮以使用Java™ Web Start运行 MultiListener(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
- 点击Blah blah blah按钮。只有
MultiListener
对象被注册来监听这个按钮。 - 点击*You do not say!*按钮。
MultiListener
对象和Eavesdropper
对象都被注册来监听这个按钮。
你可以在MultiListener.java
中找到整个程序。这里是实现按钮事件处理的代码:
public class MultiListener ... implements ActionListener { ... *//where initialization occurs:* button1.addActionListener(this); button2.addActionListener(this); button2.addActionListener(new Eavesdropper(bottomTextArea)); } public void actionPerformed(ActionEvent e) { topTextArea.append(e.getActionCommand() + newline); } } class Eavesdropper implements ActionListener { ... public void actionPerformed(ActionEvent e) { myTextArea.append(e.getActionCommand() + newline); } }
在上述代码中,MultiListener
和Eavesdropper
都实现了ActionListener
接口,并使用JButton
的addActionListener
方法注册为动作监听器。这两个类的actionPerformed
方法的实现类似:它们只是将事件的动作命令添加到文本区域中。
编写事件监听器的一般信息
当设计程序时,您可能希望在一个不是公共的类中实现您的事件监听器,而是在更隐蔽的地方。私有实现是更安全的实现。
本节讨论了在应用程序中实现事件处理程序时要牢记的几个设计考虑。然后我们向您介绍了事件对象,这些小对象描述了每个事件。特别是,我们谈到了低级事件和语义事件的概念,建议您在可能的情况下优先选择语义事件。本节的其余部分讨论了您可能在一些事件监听器中使用的实现技术,或者在其他人或 GUI 构建器创建的事件监听器中看到的技术。
- 设计考虑
- 获取事件信息:事件对象
- 概念:低级事件和语义事件
- 事件适配器
- 获取事件信息:事件对象
- 原文:
docs.oracle.com/javase/tutorial/uiswing/events/generalrules.html
设计考虑
关于事件监听器最重要的规则是它们应该执行得非常快速。因为所有绘图和事件监听方法都在同一个线程中执行,一个慢速的事件监听器方法会使程序看起来无响应且重绘速度缓慢。如果需要作为事件结果执行一些耗时操作,请通过启动另一个线程(或以某种方式发送请求到另一个线程)来执行操作。有关使用线程的帮助,请参阅 Swing 中的并发性。
您有许多选择来实现事件监听器。我们无法推荐特定的方法,因为一个解决方案并不适用于所有情况。但是,我们可以给您一些提示,并展示一些您可能看到的技术,即使您在程序中没有使用相同的解决方案。
例如,您可能选择为不同类型的事件监听器实现单独的类。这种架构易于维护,但许多类也可能意味着性能降低。
内部类和匿名内部类
如果您有一种非常特定的简单事件监听器,您可能可以通过使用EventHandler
类而根本不创建类来避免。
EventHandler 类
每个事件监听器方法都有一个参数,即从EventObject
类继承的对象。尽管参数总是继承自EventObject
,但其类型通常更加精确地指定。例如,处理鼠标事件的方法的参数是MouseEvent
的实例,其中MouseEvent
是EventObject
的间接子类。
EventObject
类定义了一个非常有用的方法:
Object getSource()
返回触发事件的对象。
注意,getSource
方法返回一个 Object
。事件类有时定义类似于 getSource
的方法,但返回类型更受限制。例如,ComponentEvent
类定义了一个 getComponent
方法,就像 getSource
一样返回触发事件的对象。不同之处在于 getComponent
总是返回一个 Component
。每个事件监听器的操作页面都会提到你应该使用 getSource
还是其他方法来获取事件源。
通常,事件类定义了返回有关事件信息的方法。例如,你可以查询 MouseEvent
对象以获取有关事件发生位置、用户点击次数、按下的修改键等信息。
概念:低级事件和语义事件
事件可以分为两组:低级 事件和 语义 事件。低级事件代表窗口系统发生或低级输入。其他一切都是语义事件。
低级事件的示例包括鼠标和键盘事件,这两者都直接由用户输入引起。语义事件的示例包括动作和项目事件。语义事件可能由用户输入触发;例如,当用户点击按钮时,按钮通常会触发动作事件,当用户按下 Enter 键时,文本字段会触发动作事件。然而,有些语义事件根本不是由低级事件触发的。例如,当表模型从数据库接收新数据时,可能会触发表模型事件。
尽可能监听语义事件而不是低级事件。这样,你可以使你的代码尽可能健壮和可移植。例如,监听按钮上的动作事件,而不是鼠标事件,意味着当用户尝试使用键盘替代方案或外观特定手势激活按钮时,按钮将做出适当反应。处理复合组件(如组合框)时,必须坚持使用语义事件,因为你没有可靠的方法在所有可能用于形成复合组件的外观特定组件上注册监听器。
事件适配器
一些监听器接口包含多个方法。例如,MouseListener
接口包含五个方法:mousePressed
、mouseReleased
、mouseEntered
、mouseExited
和 mouseClicked
。即使你只关心鼠标点击,如果你的类直接实现了 MouseListener
,那么你必须实现所有五个 MouseListener
方法。对于你不关心的事件,方法可以留空。这里是一个例子:
//An example that implements a listener interface directly. public class MyClass implements MouseListener { ... someObject.addMouseListener(this); ... /* Empty method definition. */ public void mousePressed(MouseEvent e) { } /* Empty method definition. */ public void mouseReleased(MouseEvent e) { } /* Empty method definition. */ public void mouseEntered(MouseEvent e) { } /* Empty method definition. */ public void mouseExited(MouseEvent e) { } public void mouseClicked(MouseEvent e) { *...//Event listener implementation goes here...* } }
一系列空方法体的结果可能会使代码更难阅读和维护。为了帮助您避免实现空方法体,API 通常为每个具有多个方法的监听器接口包含一个适配器类。(Listener API Table 列出了所有监听器及其适配器。)例如,MouseAdapter
类实现了MouseListener
接口。适配器类实现了其接口所有方法的空版本。
要使用适配器,您创建一个它的子类并仅重写感兴趣的方法,而不是直接实现监听器接口的所有方法。以下是修改前述代码以扩展MouseAdapter
的示例。通过扩展MouseAdapter
,它继承了MouseListener
包含的所有五个方法的空定义。
/* * An example of extending an adapter class instead of * directly implementing a listener interface. */ public class MyClass extends MouseAdapter { ... someObject.addMouseListener(this); ... public void mouseClicked(MouseEvent e) { *...//Event listener implementation goes here...* } }
内部类和匿名内部类
如果您想使用适配器类,但不希望您的公共类继承自适配器类怎么办?例如,假设您编写了一个小程序,并且希望您的Applet
子类包含一些处理鼠标事件的代码。由于 Java 语言不允许多重继承,您的类不能同时扩展Applet
和MouseAdapter
类。解决方案是定义一个内部类,即在Applet
子类内部扩展MouseAdapter
类。
内部类还可以用于直接实现一个或多个接口的事件监听器。
//An example of using an inner class. public class MyClass extends Applet { ... someObject.addMouseListener(new MyAdapter()); ... class MyAdapter extends MouseAdapter { public void mouseClicked(MouseEvent e) { *...//Event listener implementation goes here...* } } }
性能注意:
在考虑是否使用内部类时,请记住应用程序启动时间和内存占用量通常与加载的类数量成正比。您创建的类越多,程序启动时间越长,占用的内存也越多。作为应用程序开发人员,您必须在其他设计约束条件下平衡这一点。我们并不建议您将应用程序变成一个单一的庞大类,希望缩短启动时间和内存占用量,这将导致不必要的麻烦和维护负担。
您可以创建一个不指定名称的内部类,这被称为匿名内部类。虽然乍一看可能会觉得奇怪,但匿名内部类可以使您的代码更易于阅读,因为类是在引用它的地方定义的。但是,您需要权衡方便性与增加类数量可能带来的性能影响。
以下是使用匿名内部类的示例:
//An example of using an anonymous inner class. public class MyClass extends Applet { ... someObject.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { *...//Event listener implementation goes here...* } }); ... } }
注意:
匿名内部类的一个缺点是它们无法被长期持久性机制所看到。有关更多信息,请参阅JavaBeans™ package的 API 文档以及 JavaBeans trail 中的 Bean Persistence 课程。
内部类即使需要访问封闭类的私有实例变量,也可以正常工作。只要不将内部类声明为static
,内部类就可以像在包含类中一样引用实例变量和方法。要使局部变量对内部类可用,只需将变量的副本保存为final
局部变量。
要引用封闭实例,可以使用*EnclosingClass*.this
。有关内部类的更多信息,请参见 Nested Classes。
EventHandler 类
EventHandler
类支持简单、一语句事件监听器的动态生成。虽然EventHandler
仅对一种极其简单的事件监听器类型有用,但由于两个原因值得一提。它适用于:
- 创建一个事件监听器,持久性可以看到,但不会用事件监听器接口和方法堵塞自己的类。
- 不增加应用程序中定义的类的数量可以提高性能。
手动创建EventHandler
很困难。EventHandler
必须小心构建。如果出错,编译时不会通知您,它会在运行时抛出一个晦涩的异常。因此,最好由 GUI 构建器创建EventHandler
。EventHandler
应该仔细记录。否则,您可能会产生难以阅读的代码。
EventHandler
类旨在供交互式工具使用,例如应用程序构建器,允许开发人员在 bean 之间建立连接。通常,连接是从用户界面 bean(事件源)到应用程序逻辑 bean(目标)进行的。这种连接的最有效方式是将应用程序逻辑与用户界面隔离开来。例如,从 JCheckBox 到接受布尔值的方法的连接的EventHandler
可以处理提取复选框状态并直接传递给方法,使方法与用户界面层隔离开来。
内部类是处理用户界面事件的另一种更通用的方式。EventHandler
类仅处理使用内部类可能实现的一部分功能。但是,EventHandler
与长期持久性方案的配合效果比内部类更好。此外,在大型应用程序中,使用EventHandler
可以减少应用程序的磁盘和内存占用。
使用EventHandler
的示例EventHandler
的最简单用法是安装一个调用目标对象上没有参数的方法的监听器。在以下示例中,我们创建一个 ActionListener,调用javax.swing.JFrame
实例上的toFront
方法。
myButton.addActionListener( (ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));
当按下myButton
时,语句frame.toFront()
将被执行。通过定义ActionListener
接口的新实现并将其实例添加到按钮中,也可以获得相同的效果,并获得一些额外的编译时类型安全性:
//Equivalent code using an inner class instead of EventHandler. myButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { frame.toFront(); } });
下一个最简单的EventHandler
的用法是从监听器接口的方法的第一个参数(通常是事件对象)中提取一个属性值,并将其用于设置目标对象的属性值。在下面的示例中,我们创建了一个ActionListener
,将目标对象(myButton)的nextFocusableComponent
属性设置为事件的source
属性的值。
EventHandler.create(ActionListener.class, myButton, "nextFocusableComponent", "source")
这对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler. new ActionListener() { public void actionPerformed(ActionEvent e) { myButton.setNextFocusableComponent((Component)e.getSource()); } }
也可以创建一个仅将传入事件对象传递给目标动作的EventHandler
。如果第四个EventHandler.create
参数是一个空字符串,那么事件就会被简单地传递下去:
EventHandler.create(ActionListener.class, target, "doActionEvent", "")
这对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler. new ActionListener() { public void actionPerformed(ActionEvent e) { target.doActionEvent(e); } }
可能最常见的EventHandler
用法是从事件对象的源中提取一个属性值,并将此值设置为目标对象的属性值。在下面的示例中,我们创建了一个ActionListener
,将目标对象的label
属性设置为事件源(事件的source
属性的值)的text
属性的值。
EventHandler.create(ActionListener.class, myButton, "label", "source.text")
这对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler. new ActionListener { public void actionPerformed(ActionEvent e) { myButton.setLabel(((JTextField)e.getSource()).getText()); } }
Swing 组件支持的监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/eventsandcomponents.html
您可以通过查看可以在组件上注册的事件监听器的类型来了解组件可以触发哪些事件。例如,JComboBox
类定义了以下监听器注册方法:
addActionListener
addItemListener
addPopupMenuListener
因此,组合框除了继承自JComponent
的监听器方法外,还支持动作、项目和上下文菜单监听器。
Swing 组件支持的监听器分为两类:
- 所有 Swing 组件都支持的监听器
- 其他 Swing 组件支持的监听器
所有 Swing 组件都支持的监听器
因为所有 Swing 组件都是从 AWT Component
类继承的,您可以在任何 Swing 组件上注册以下监听器:
组件监听器
监听组件的大小、位置或可见性的变化。
焦点监听器
监听组件是否获得或失去键盘焦点。
键盘监听器
监听键盘按键;键盘事件仅由当前具有键盘焦点的组件触发。
鼠标监听器
监听鼠标点击、鼠标按下、鼠标释放以及鼠标移动进入或离开组件的绘图区域。
鼠标移动监听器
监听鼠标光标在组件上的位置变化。
鼠标滚轮监听器
监听鼠标滚轮在组件上的移动。
监听组件的包含层次结构的更改事件。
监听组件的包含层次结构的移动和调整事件的变化。
所有 Swing 组件都是从 AWT Container
类继承的,但其中许多并不用作容器。因此,从技术上讲,任何 Swing 组件都可以触发容器事件,通知监听器组件已被添加到容器或从容器中移除。然而,实际上,只有容器(如面板和框架)和复合组件(如组合框)通常会触发容器事件。
JComponent
提供对另外三种监听器类型的支持。您可以注册一个祖先监听器,以便在组件的包含祖先被添加到容器、移除、隐藏、显示或移动时收到通知。这种监听器类型是一个实现细节,早于层次监听器。
另外两种监听器类型是 Swing 组件符合 JavaBeans 规范的一部分。这意味着 Swing 组件支持绑定和约束属性,并通知监听器属性的更改。属性更改监听器 监听绑定属性的更改,并被几个 Swing 组件使用,例如 格式化文本字段,以跟踪组件的绑定属性的更改。此外,属性更改监听器以及 可否决更改监听器 被构建工具用于监听约束属性的更改。有关更多信息,请参考 JavaBeans 路径中的 属性 课程。
Swing 组件支持的其他监听器
以下表格列出了 Swing 组件及其支持的专用监听器,不包括所有 Component
、Container
或 JComponent
支持的监听器。在许多情况下,事件直接从组件中触发。在其他情况下,事件是从组件的数据或选择模型中触发的。要了解您感兴趣的特定组件和监听器的详细信息,请首先转到组件的操作指南部分,然后如有必要再转到监听器的操作指南部分。
此表列出了带有其专用监听器的 Swing 组件
| 表格 | | | | | | | | 表格模型 表格列模型
单元格编辑器 |
文本区域 | ||||||||
文本字段 | ||||||||
文本面板 | 超链接 | |||||||
切换按钮 |
| 树 | | | | | | | | 树展开 树将展开
树模型
树选择 |
视口(被滚动窗格使用) |
实现常见事件的监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/handling.html
以下部分详细介绍了实现特定类型事件监听器的细节。我们并没有为您可以编写的每一种事件监听器都提供操作指南。相反,我们涵盖了我们认为您最有可能需要的监听器。如果您对其他监听器感兴趣,您可以在监听器 API 表中找到一些信息。
- 如何编写动作监听器
- 如何编写插入符监听器
- 如何编写变更监听器
- 如何编写组件监听器
- 如何编写容器监听器
- 如何编写文档监听器
- 如何编写焦点监听器
- 如何编写内部窗口监听器
- 如何编写项目监听器
- 如何编写键盘监听器
- 如何编写列表数据监听器
- 如何编写列表选择监听器
- 如何编写鼠标监听器
- 如何编写鼠标移动监听器
- 如何编写鼠标滚轮监听器
- 如何编写属性更改监听器
- 如何编写表格模型监听器
- 如何编写树展开监听器
- 如何编写树模型监听器
- 如何编写树选择监听器
- 如何编写树展开监听器
- 如何编写可撤销编辑监听器
- 如何编写窗口监听器
如何编写一个动作监听器
原文:
docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html
动作监听器可能是最容易实现的事件处理程序,也是最常见的。您实现一个动作监听器来定义用户执行某些操作时应该执行的操作。
每当用户执行操作时,都会发生一个动作事件。例如:当用户点击一个按钮,选择一个菜单项,在文本字段中按 Enter 键。结果是向所有注册在相关组件上的动作监听器发送一个actionPerformed
消息。
要编写一个动作监听器,请按照以下步骤进行:
- 声明一个事件处理程序类,并指定该类要么实现 ActionListener 接口,要么扩展实现 ActionListener 接口的类。例如:
public class MyClass implements ActionListener {
- 在一个或多个组件上注册事件处理程序类的实例作为监听器。例如:
someComponent.addActionListener(instanceOfMyClass);
- 包含实现监听器接口中方法的代码。例如:
public void actionPerformed(ActionEvent e) { ...//code that reacts to the action... }
Java 中文官方教程 2022 版(二十四)(4)https://developer.aliyun.com/article/1486800