【Swing】事件处理
前言
当用户点击图形界面上的一个按钮或者其他Component时要有所响应,这才是实现了图形界面的交互功能。如何做出这些响应我们就需要了解事件的处理机制。下面将分为以下内容介绍AWT(Swing)中事件处理机制:
什么是事件
通俗一点来说就是某种状态的改变,在我们的图形界面中就表现为某个按钮被点击了,窗口被关闭了等。
什么是事件处理
当某个事件发生时(界面中的某个Component的某个状态发生改变时),我们希望在这个时机执行一些代码来做我们希望做的事,这个就是事件处理。如点击窗口关闭按钮时,弹出对话框询问用户是否保存当前已经修改过的内容。
Java是面向对象的编程语言,Java中使用监听器类来探测一个事件(改变),使用监听器类中的方法来在事件发生的时候处理事件。
事件处理中的三要素
事件源:是这个对象的状态改变引发的事件,事件源通常是Component。
事件:事件源发生的状态改变。如按钮被鼠标左击或者被鼠标右击等。
事件监听器:监听器被安装在某个Component上,负责监听这个Component具体状态被改变了。
AWT中事件处理的流程
外部诱使事件源状态发生变化,产生事件对象,然后事件监听器监听到该事件的发生,做出响应。
- 首先将事件监听器注册到事件源上面
- 触发事件源上的事件(改变状态)
- 生成事件对象
- 事件监听器监听到该事件的发生,生成的事件对象当做参数传入事件处理器(监听器类中的方法)
- 调用事件处理器做出响应
举例点击事件
鼠标点击按钮后,文本框中显示一行‘按钮被点击了'。
先是在按钮上注册了事件监听器,监听器中设置鼠标被点击时应该调用的事件处理器是怎么处理的;然后,鼠标点击按钮;生成按钮被点击的事件对象;事件监听器监听到点击事件发生,就会传入事件对象到事件处理器;最后,调用事件处理器中做出希望的响应。
public class TestEvent {
public static void main(String[] args) {
JFrame myFrame = new JFrame();
JButton btn = new JButton("点击我");
JTextField field = new JTextField();
//为field指定宽高
field.setPreferredSize(new Dimension(100, 40));
//使用Jpanel容器装载JButton和JTextFiedl组件
JPanel jPanel = new JPanel();
jPanel.add(btn);
jPanel.add(field);
myFrame.add(jPanel);
//设置窗口的大小宽高都为300像素以及位置距离屏幕左上方向右300像素以及向下300像素
myFrame.setBounds(300, 300, 300, 300);
//必须设置这个属性 才可以看见窗口
myFrame.setVisible(true);
//为btn设置事件监听器 监听器采用匿名内部类的形式
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
field.setText("Button被点击了");
}
});
}
}
AWT中事件处理的模板
class XXXXListener implements XXXListener {
@Override
public void xxx(ActionEvent e) {
System.out.println("用户单击了按钮");
}
}
…
yyy.addXXXListener(new XXXXListener());
创建一个监听器类,该类实现监听器接口XXXListener
,然后实现监听器方法使得可以处理对应事件
-
在感兴趣的Component上使用
addXXXListener(...)
,添加监听器即传入监听器的实例 -
注意:
不同的Component有不同的事件发生,但是一般添加监听器的方法都是
addXXXListener(...)
。Component会有不同的事件发生,所以需要对感兴趣的事件添加对应的监听器。有些监听器内部会有多个方法,可以监听多种事件,但是这些事件一般都相关。在点击事件举例中,我们添加监听器采用的匿名内部类的形式,可以看出监听器的实现形式还是有多种。
监听器(EventListener)的实现形式
监听器是一种特殊的Java类。在AWT中,监听器主要有以下几种实现方式:
监听器作为外部类
规范易于理解、类本身可以重用;不足在于一般情况下不利于实现事件处理中的功能,因为不易于访问界面中的属性和方法
class BtnListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
//使用getSource获取事件源对象 但是不易访问到其他组件
JButton btn = (JButton)e.getSource();
btn.setText("我被点击了");
}
}
public class TestListener {
private JFrame myFrame = new JFrame("测试外部监听器");
private JButton btn = new JButton("点击我");
private JTextField field = new JTextField();
//构造函数
public TestListener() {
init();
}
//初始化 为按钮添加事件监听器
public void init() {
JPanel jPanel = new JPanel();
jPanel.add(btn);
field.setPreferredSize(new Dimension(100, 40));
jPanel.add(field);
//以创建外部类对象方式添加事件监听器
btn.addActionListener(new BtnListener());
myFrame.add(jPanel);
myFrame.setBounds(300, 300, 500, 300);
//使得按钮窗口的关闭按钮可以关闭窗口
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//显示窗口
public void showFrame() {
myFrame.setVisible(true);
}
public static void main(String[] args) {
new TestListener().showFrame();
}
}
监听器作为内部类
可以方便的访问主类中的任何属性和方法,包括私有方法;不足在于使得主类过于复杂、不可以在不同界面中重用
public class TestListener {
//内部类
class BtnListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
//可以方便操作主类的其他属性
field.setText("Btn被点击了");
}
}
...
//初始化 为按钮添加事件监听器
public void init() {
...
//以创建内部类对象方式添加事件监听器
btn.addActionListener(new BtnListener());
...
}
...
}
监听器作为主类本身
可以方便地访问本类中的任何方法和属性;不足在于使得本类的方法过多
//主类作为监听器
public class TestListener implements ActionListener{
private JFrame myFrame = new JFrame("测试主类作为监听器");
private JButton btn = new JButton("点击我");
private JTextField field = new JTextField();
//构造函数
public TestListener() {
init();
}
//实现的处理方法
@Override
public void actionPerformed(ActionEvent e) {
field.setText("Btn被点击了");
}
//初始化 为按钮添加事件监听器
public void init() {
JPanel jPanel = new JPanel();
jPanel.add(btn);
field.setPreferredSize(new Dimension(100, 40));
jPanel.add(field);
//主类本身作为监听器对象传入
btn.addActionListener(this);
myFrame.add(jPanel);
myFrame.setBounds(300, 300, 500, 300);
//使得按钮窗口的关闭按钮可以关闭窗口
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//显示窗口
public void showFrame() {
myFrame.setVisible(true);
}
public static void main(String[] args) {
new TestListener().showFrame();
}
}
监听器作为匿名内部类
可以方便地访问主类的方法和属性;不足在于对于每个事件都需要写匿名内部类,不能重用、不利于理解
//主类作为监听器
public class TestListener{
private JFrame myFrame = new JFrame("测试主类作为监听器");
private JButton btn = new JButton("点击我");
private JTextField field = new JTextField();
//构造函数
public TestListener() {
init();
}
//初始化 为按钮添加事件监听器
public void init() {
JPanel jPanel = new JPanel();
jPanel.add(btn);
field.setPreferredSize(new Dimension(100, 40));
jPanel.add(field);
//以匿名内部类的方式创建监听器
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
field.setText("Btn被点击了");
}
});
//或者使用lambda表达式
// btn.addActionListener(event->field.setText("Btn被点击了"));
myFrame.add(jPanel);
myFrame.setBounds(300, 300, 500, 300);
//使得按钮窗口的关闭按钮可以关闭窗口
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//显示窗口
public void showFrame() {
myFrame.setVisible(true);
}
public static void main(String[] args) {
new TestListener().showFrame();
}
}
关于使用lambda表达式的理解:若某个方法使用的类是“众所周知“的并且类中的方法是唯一的,我们就只需要给出参数和一些执行代码代替原来复杂的一堆代码。因为唯一性,编译器就会推断出使用的是什么类以及类中的方法。
上面我们可以使用lamnda表达式event->field.setText(...)
代替了匿名内部类那么多代码,那是因为编译器可以推断出addListener(...)
需要传入的参数是实现了ActionListener
接口的类,这个类中有唯一的方法就是actionPerformed
以及这个方法只有一个参数而且也知道其参数类型。
使用lambda表达式虽然可以简化我们的代码,但是我们需要理清楚其原本应该使用什么类以及什么方法。
若是省略的方法由多个参数则->
左边的参数需要使用()
括起来如(a,b,c)
,->
后有多行需要执行的代码则使用{}
将代码括起来。
事件适配器(EventAdapter)
我们在创建监听器类时需要实现其实现的相应监听器接口中的所有方法。前面提到过,某些监听器接口内会有多个方法,但是我们只对一些方法感兴趣,有没有方法只用实现感兴趣的方法其他方法就不实现呢。答案是有的,那就是使用事件适配器。Java内部已经帮我们事先写了一些适配器,我们只需要实现感兴趣的适配器,然后重写我们感兴趣的方法。
若是没有适配器,我们想监听鼠标点击事件,我们先看MouseListener接口的源码实现:
public interface MouseListener extends EventListener {
/**
* Invoked when the mouse button has been clicked (pressed
* and released) on a component.
*/
public void mouseClicked(MouseEvent e);
/**
* Invoked when a mouse button has been pressed on a component.
*/
public void mousePressed(MouseEvent e);
/**
* Invoked when a mouse button has been released on a component.
*/
public void mouseReleased(MouseEvent e);
/**
* Invoked when the mouse enters a component.
*/
public void mouseEntered(MouseEvent e);
/**
* Invoked when the mouse exits a component.
*/
public void mouseExited(MouseEvent e);
}
我们需要实现mouseClicked方法以外的其他4个方法,可以说是非常麻烦的。我们再看MouseAdapter的源码实现:
public abstract class MouseAdapter implements MouseListener, MouseWheelListener, MouseMotionListener {
/**
* {@inheritDoc}
*/
public void mouseClicked(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mousePressed(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mouseReleased(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mouseEntered(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mouseExited(MouseEvent e) {}
/**
* {@inheritDoc}
* @since 1.6
*/
public void mouseWheelMoved(MouseWheelEvent e){}
/**
* {@inheritDoc}
* @since 1.6
*/
public void mouseDragged(MouseEvent e){}
/**
* {@inheritDoc}
* @since 1.6
*/
public void mouseMoved(MouseEvent e){}
}
可以看出适配器的实现非常巧妙,为每个方法添加一个空的方法体而为我们进行一次封装,我们只需重写我们感兴趣的方法就可,其他方法也不会受到影响。
AWT中的事件分类
AWT中的事件主要分为两大类:低级事件和高级事件
低级事件
低级事件就比较底层(比较细节),主要有:
-
ComponentEvent:组件事件,当组件尺寸发生改变、位置发生变化、显示/隐藏状态发生改变时,就会触发该事件
-
ContainerEvent:容器事件,当容器里增加、删除组件时,就会触发该事件
-
WindowEvent:窗口事件,当窗口状态发生改变(打开、关闭、最大化、最小化)时,就会触发该事件
-
FocusEvent:焦点事件,当组件得到焦点或者失去焦点时,就会触发该事件
-
KeyEvent:键盘事件,当键盘按键被按下、松开时就会触发该事件
-
MouseEvent:鼠标事件,当一个组件被鼠标按下、放开、在其上面移动鼠标时,就会触发该事件
-
PaintEvent:绘制事件,当GUI组件调用update()/paint()方法时触发该事件,该事件并非专用于事件处理模型中,一般也很少直接监听该事件
高级事件
高级事件基于语义,并不和特定的动作相关,而依赖于触发该事件的组件类别。主要分为:
- ActionEvent:动作事件,当按钮、菜单等能产生Action的项目被单击,或者在TextField中按下Enter按钮时,就会触发该事件
- AdjustmentEvent:调节事件,在滑动条上移动滑块调节数值时触发该事件
- ItemEvent:–选项事件,当在有很多项目的组件中,选中或者取消选中了某一个项目,就会触发该事件
- TextEvent:文本事件,当文本框或者文本域这类具有文本的组件中,文本发生变化时,就会触发该事件
小结
总结了关于事件处理的大部分基础,基础概念有了,重要的还是多写代码实践。另外事件处理中还是涉及到很多技巧,比如使用lambda表达式、使用设计模式的适配器思想去简化设置监听器的代码。在自己写的过程中才会体会更多