目录
图形用户界面要响应用户的操作,例如,单击按钮或移动鼠标等,就必须提供相应的事件处理机制。Java Swing采用AWT事件处理模型,在事件处理过程中涉及的主要概念有事件、事件源、事件处理方法、事件监听器类和事件监听器接口等。
1. 事件处理机制
事件是用户在界面上的一个操作,通常使用各种输入设备来实现,例如,鼠标、键盘等。当一个事件发生时,该事件用一个事件对象来表示。事件对象有对应的事件类,不同的事件类描述不同类型的用户动作。事件类包含在java.awt.event和javax.swing.event包中,例如,单击按钮事件类是java.awt.event.ActionEvent。每个事件类对应一个接口,该接口称为监听器接口,它规定了接收并处理该类事件的方法规范。例如,对应于ActionEvent事件,有监听器接口ActionListener,该接口中只定义了一个方法:public void actionPerformed(ActionEvent e),当出现ActionEvent事件时,该方法被调用。
产生事件的组件叫做事件源。例如,在一个按钮上单击鼠标时,该按钮就是事件源,该动作会产生一个ActionEvent类型的事件对象。在Java中,事件处理者是一个接收事件对象并进行相应处理的方法,这个方法包含在一个对象中,该对象称为事件监听器,其所对应的类称为事件监听器类。事件监听器负责检查事件是否发生,若发生就激活事件处理方法进行处理。事件监听器类必须实现相应的事件监听器接口或者继承某个事件监听器适配器类。事件监听器接口定义了处理事件必须实现的方法,事件监听器适配器类是对事件监听器接口的简单实现,目的是为了减少编程的工作量。
为了能够让事件监听器检查某个组件(事件源)是否发生了某些事件,并且在发生时激活事件处理方法进行相应的处理,必须在事件源上注册事件监听器,这是通过使用事件源组件的方法addXxxListener(事件监听器)来实现的,其中,Xxx为相应的事件类。例如,在JButton类中有如下方法:
public void addActionListener(ActionListener l);
该方法可以为Jbutton组件注册ActionListener事件监听器。
【例1】下面代码演示使用内部类和匿名类的方式实现ActionEvent事件处理。
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class ActionEventDemo extends JFrame{
JLabel label=new JLabel("Hello!");
JButton button1=new JButton("确定");
JButton button2=new JButton("取消");
JPanel panel=new JPanel();
public ActionEventDemo() {
this.setTitle("我的窗口");
this.setSize(300,200);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane=this.getContentPane();
panel.add(button1);
panel.add(button2);
//注册button1的事件事件监听器(内部类的方式)
button1.addActionListener(new ActionEventHandler());
//注册button2的事件事件监听器(匿名类的方式)
button2.addActionListener(new ActionListener() {
//事件处理方法
public void actionPerformed(ActionEvent e) {
label.setText("你单击取消按钮");
}
});
contentPane.add(label,BorderLayout.CENTER);
contentPane.add(panel,BorderLayout.SOUTH);
this.setVisible(true);
}
//事件监听器类(内部类)
class ActionEventHandler implements ActionListener{
//事件处理方法
public void actionPerformed(ActionEvent e) {
label.setText("你单击确定按钮");
}
}
public static void main(String[] args) {
ActionEventDemo frame=new ActionEventDemo();
}
}
运行上述程序,显示结果如图1(a)所示。单击确定按钮,显示结果如图1(b),单击取消按钮,显示结果如图1(c)所示。该程序的第22行和第24行分别是注册按钮button1和button2的事件监听器,其中,第22行的事件监听器是一个内部类ActionEventHandler的实例,第24行的事件监听器是一个匿名类。
(a) | (b) | (c) |
图1. ActionEvent事件处理示例
【例2】下面代码演示采用实现ActionListener接口的方式处理ActionEvent事件。
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class ActionEventDemo extends JFrame implements ActionListener{
JLabel label=new JLabel("Hello!");
JButton button1=new JButton("确定");
JButton button2=new JButton("取消");
JPanel panel=new JPanel();
public ActionEventDemo() {
this.setTitle("我的窗口");
this.setSize(300,200);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane=this.getContentPane();
panel.add(button1);
panel.add(button2);
//注册事件监听器(本对象)
button1.addActionListener(this);
button2.addActionListener(this);
contentPane.add(label,BorderLayout.CENTER);
contentPane.add(panel,BorderLayout.SOUTH);
this.setVisible(true);
}
//事件处理方法
public void actionPerformed(ActionEvent e) {
String command=e.getActionCommand();
if(command.equals("确定"))
label.setText("你单击确定按钮");
else if(command.equals("取消"))
label.setText("你单击取消按钮");
}
public static void main(String[] args) {
ActionEventDemo frame=new ActionEventDemo();
}
}
上述程序的运行结果与例1是一致的。由于ActionEventDemo类实现了ActionListener接口,因此,ActionEventDemo就是一个事件监听器类,该类必须实现事件处理方法actionPerformed(ActionEvent e)。ActionEventDemo类的对象就是一个事件监听器,所以,按钮button1和button2所注册的事件监听器是this。
2. 事件的种类
在上节介绍的图形用户界面事件处理机制中,只涉及了ActionEvent一类事件。实际上,在java.awt.event包和javax.swing.event包中还定义了很多其他类型的事件。每一类事件对应有一个事件监听器接口,该接口定义了接收和处理事件的抽象方法。实现该接口的类就是事件监听器类,其对象可作为事件监听器向相应的组件注册。事件的类名通常为XxxEvent,对应的事件监听器接口名通常为XxxListener。表1列出了一些常用的事件类型、与之相对应的事件监听器接口以及接口中声明的抽象事件处理方法。
表1. 常用事件的接口和方法
事件类型 | 事件监听器接口名 | 事件处理方法 | 含义 |
Action | ActionListener | ActionPerformed(ActionEvent) | 动作事件,使用该类事件响应用户的操作,例如单击按钮 |
Item | ItemListener | ItemStateChanaged(ItemEvent) | 选项事件,当用户选择或操作列表中的某个选项时触发该类事件 |
Mouse | MouseListener | mousePressed(MouseEvent) mouseReleased(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mouseClicked(MouseEvent) | 鼠标事件,使用该事件捕捉和处理鼠标的动作 |
MouseMotion | MouseMotionListener | mouseDragged(MouseEvent) mouseMoved(MouseEvent) | 鼠标运动事件,当鼠标在组件上移动时被触发 |
Key | KeyListener | keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) | 键盘处理事件,当按下、释放或键入某个键时触发此类事件 |
Foucs | FoucsListener | focusGained(FoucsEvent) foucsLost(FoucsEvent) | 焦点事件,当组件获得或失去焦点时触发的事件 |
Component | ComponentListener | componentMoved(ComponentEvent) componentHidden(ComponentEvent) componentResized(ComponentEvent) componentShown(ComponentEvent) | 组件事件,当组件移动、更改大小或可见性时触发的事件 |
Window | WindowListener | windowClosing(WindowEvent) windowOpened(WindowEvent) windowIconified(WindowEvent) windowDeiconified(WindowEvent) windowClosed(WindowEvent) windowActived(WindowEvent) windowDeactivated(WindowEvent) | 窗口事件,当Window窗口被打开、关闭、最小化、最大化、激活时被触发 |
Container | ContainerListener | componentAdded(ContainerEvent) componentRemoved(ContainerEvent) | 容器事件,当向容器中添加或移除组件时被触发 |
Adjustment | AdjustmentListener | adjustmentValueChanged( AdjustmentEvent) | 处理滚动条事件,当用户拖动滚动条或通过键盘操作滚动条时触发该类事件 |
Text | TextListener | textValueChanged(TextEvent) | 文本事件,当用户与文本组件交互时触发的事件 |
TreeSelection | TreeSelectionListener | valueChanged(TreeSelectionEvent) | 树选择事件,当用户选择树节点时触发的事件 |
下面以Mouse和MouseMotion为例来说事件的处理。Mouse对应的事件监听器接口为MouseListener,该接口包含5种处理鼠标事件MouseEvent的抽象方法:
- mousePressed(MouseEvent e):负责处理在组件上按下鼠标键触发的事件;
- mouseReleased(MouseEvent e):负责处理在组件上释放鼠标键触发的事件;
- mouseEntered(MouseEvent e):负责处理鼠标进入组件触发的事件;
- mouseExited(MouseEvent e):负责处理鼠标离开组件触发的事件;
- mouseClicked(MouseEvent e):负责处理在组件上单击鼠标的事件。
MouseMotion对应的事件监听器接口为MouseMotionListener,该接口包含2种处理鼠标事件MouseEvent的抽象方法:
- mouseDragged(MouseEvent e):负责处理拖动鼠标触发的事件;
- mouseMoved(MouseEvent e):负责处理移动鼠标触发的事件。
MouseEvent中包含如下一些处理重要的方法:
- getX():获得鼠标指针在事件源坐标系中的x轴坐标;
- getY():获得鼠标指针在事件源坐标系中的y轴坐标;
- getClikCount():获取鼠标被单击的次数。
- getSource():获取鼠标事件的事件源。
【例3】鼠标事件的处理。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MouseEventDemo extends JFrame implements
MouseListener,MouseMotionListener{
JTextField textField=new JTextField();//创建文本框
JTextArea textArea=new JTextArea();//创建文本区
JPanel panel=new JPanel();//创建面板
JButton button1=new JButton("Clear");//创建按钮
JButton button2=new JButton("Exit");//创建按钮
public MouseEventDemo() {
setTitle("我的窗口");
setSize(400,300);
setLocation(200, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane=this.getContentPane();
panel.add(button1);
panel.add(button2);
//注册鼠标事件监听器(窗口、文本框、文本区、按钮)
addMouseListener(this);
textField.addMouseListener(this);
textArea.addMouseListener(this);
button1.addMouseListener(this);
button2.addMouseListener(this);
//注册鼠标运动事件监听器(窗口、文本区)
addMouseMotionListener(this);
textArea.addMouseMotionListener(this);
//注册动作事件监听器(按钮)
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
textArea.setText("");//清空文本区
}
});
button2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);//退出系统
}
});
contentPane.add(textField,BorderLayout.NORTH);
contentPane.add(new JScrollPane(textArea),BorderLayout.CENTER);
contentPane.add(panel,BorderLayout.SOUTH);
this.setVisible(true);
}
//实现MouseListener接口中的方法
public void mousePressed(MouseEvent e){
textArea.append("按下鼠标的位置:("+e.getX()+","+e.getY()+")\n");
}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {
Object obj=e.getSource();//获取事件源
if(obj instanceof JFrame) {//事件源为窗口
textField.setText("鼠标进入窗口");
}else if(obj instanceof JTextArea) {//事件源为文本区
textField.setText("鼠标进入文本区");
}else if(obj instanceof JButton) {//事件源为按钮
JButton button=(JButton) obj;
if(button==button1) {
textField.setText("鼠标进入Clear按钮");
}else {
textField.setText("鼠标进入Exit按钮");
}
}
}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {
if(e.getClickCount()>=2) {
textArea.append("双击鼠标的位置:("+e.getX()+","+e.getY()+")\n");
}
}
//实现MouseMotionListener接口中的方法
public void mouseDragged(MouseEvent e) {
textField.setText("拖动鼠标的位置:("+e.getX()+","+e.getY()+")");
}
public void mouseMoved(MouseEvent e) {}
public static void main(String[] args) {
MouseEventDemo frame=new MouseEventDemo();
}
}
在上面的例子中,MouseEventDemo类实现了两个接口MouseListener和MouseMotionListener,因此,该类既可以处理鼠标在事件源上的按下、释放、单击以及进入和退出事件源所产生的事件,也可以处理拖动鼠标和鼠标移动所产生的事件。运行上述程序,其显示结果如图1(a)所示。当鼠标移动到窗口、文本框、文本域和按钮时,激活mouseEntered()方法进行处理,并在窗口上方的文本框中显示鼠标进入的事件源。当鼠标在窗口、文本框、文本域和按钮内按下鼠标键或单击鼠标键时,激活mousePressed()和mouseClicked()方法进行处理,并在窗口中部的文本域内按下鼠标或双击鼠标的位置。当鼠标在窗口或文本域内拖动时,激活mouseDragged()方法进行处理,并在窗口上方的文本框中显示拖动鼠标的位置。鼠标的移动、单击、双击以及拖动产生的结果如图2(b)所示。当单击Clear按钮时清空文本域中的内容,当单击Exit按钮时则退出应用程序。
(a) | (b) |
图2. 鼠标事件处理示例
3. 事件适配器
在例3中,MouseEventDemo类实现了两个接口MouseListener和MouseMotionListener,因此,该类必选实现每个接口中定义的所有抽象方法。而对于不需要实现的方法,也必须写一个只有一对花括号的空方法,例如,mouseReleased()、mouseExited()和mouseMoved()方法,这会导致程序的可读性较差。
为了程序维护的方便,Java为一些声明了多个方法的XxxListener接口提供了相应的适配器类XxxAdapter,在该适配器类中实现了相应接口中的所有方法,只不过该方法的内容为空。例如,MouseListener接口的定义如下:
public interface MouseListener extends EventListener {
public void mouseClicked(MouseEvent e);
public void mousePressed(MouseEvent e);
public void mouseReleased(MouseEvent e);
public void mouseEntered(MouseEvent e);
public void mouseExited(MouseEvent e);
}
与MouseListener接口对应的适配器类为MouseAdapter,其定义如下:
public abtract class MouseListener implements MouseListener {
public void mouseClicked(MouseEvent e){}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
}
这样用户在创建事件监听器类时可以不用实现事件监听器接口,而是继承该接口所对应的适配器类,并且重载其所关心的事件处理方法。例如,假设用户只关心单击鼠标事件的处理方法,则可以采用如下代码:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class mouseClickedDemo extends MouseAdapter{
public void mouseClicked(MouseEvent e) {
//业务逻辑代码
}
}
事件是配送提供了一种实现事件监听器类的简洁方法,可以有效地减少程序的代码。但是,由于Java采用单一继承机制,当需要多种事件监听或者此类已有父类时,就无法采用事件适配器类。