Java AWT事件

本文最后更新于:2024年3月18日 凌晨

Java AWT事件

事件处理

  • 事件处理包括以下3个部分。
    • 事件源:发生事件的GUI部件。
    • 事件:用户对事件源进行操作触发事件。
    • 事件监听者:负责对事件的处理。

事件处理流程

  • 下图给出了动作事件处理的各方关系:

  • 给事件源对象注册监听者。

    • Java的事件处理机制称为委托事件处理,给事件源对象注册监听者就是发生事件时委托监听者处理事件。
  • 事件监听者是在事件发生时要对事件进行处理的对象。

    • AWT定义了各种类型的事件,每一种事件有相应的事件监听者接口,在接口中描述了处理相应事件应实现的基本行为,若事件类名为XxxEvent,则事件监听接口的命名为XxxListener,给部件注册监听者的方法为addXxxListener(XxxListener a),例如,按钮动作事件ActionEvent的监听者接口为ActionListener,给按钮注册监听者的方法为addActionListener(ActionListener a)
  • 原则上任何实现了ActionListener接口的对象,均可以作为按钮的动作事件的监听者,但要注意,选择监听者应当考虑在事件处理方法中能方便地访问相关对象。

  • 如果事件处理方法为类的成员方法就可分别访问这些成员变量,由于内嵌类也可方便访问外部类的成员,因此也常用内嵌类或匿名内嵌类的对象作为监听者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CountClick extends Frame {
Label r;// 显示结果的标签。
int value = 0;// 计数值。

public CountClick() {
super("统计按钮单击次数");// 调用父类的构造方法定义窗体的标题。
r = new Label("...结果...");
Button btn = new Button("计数");
setLayout(new FlowLayout());// 指定按流式布局排列部件。
add(btn);
add(r);
btn.addActionListener(new Process());// 将内嵌类对象作为监听者。
}
class Process implements ActionListener {// 实现ActionListener接口的内嵌类。
public void actionPerformed(ActionEvent e) {
value++;// 统计单击次数。
r.setText(" " + value);// 将结果显示在标签处。
}
}

public static void main(String[] args) {
Frame x = new CountClick();
x.setSize(400, 100);// 设置窗体的大小为长400像素,宽100像素。
x.setVisible(true);// 让窗体可见。
}
}
  • 给监听者编写事件处理代码。

    • 事件监听者的职责是实现事件的处理,监听者实现ActionListener接口必须实现接口中定义的所有方法。
    • ActionListener接口中只定义了一个方法,那就是用来完成事件处理的actionPerformed()方法,该方法中有一个ActionEvent类型的参数,在方法体中可通过该参数得到事件源对象,然后在方法体中编写具体的事件处理代码。
  • 发生事件时调用监听者的方法进行相关处理。

    • 事件源通过注册监听者的动作实现了委托登记,发生事件时就能根据登记去调用监听者的相应方法。
    • 以按钮事件源为例,在发生事件时,调用监听者对象的actionPerformed()方法,从而完成事件的处理。
    • 不难看出,接口在Java的事件处理中起了关键的约束作用,它决定了事件的发生的必定有相应的事件处理方法可供调用。

事件监听者接口及其方法

  • 图形界面的每个可能产生事件的部件称为事件源,不同事件源上发生的事件种类不同,与之相关的事件处理接口也不同。
  • Java的所有事件类都定义在java.awt.event包中,该包中还定义了11个监听者接口,每个接口内部包含了若干处理相关事件的抽象方法,如下表所示:
描述信息 接口名称 方法(事件)
单击按钮,菜单项,文本库及按回车等动作 ActionListener actionPerformed(ActionEvent)
选择了可选项的项目 ItemListener itemStateChanged(ItemEvent)
文本部件内容改变 TextListener textValueChanged(TextEvent)
移动了滚动条等部件 AdjustmentListener adjustmentValueChanged(AdjustmentEvent)
鼠标移动 MouseMotionListener mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
鼠标单击等 MouseListener mousePressed(MouseEvent)
mouseReleased(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mouseExited(MouseEvent)
mouseClicked(MouseEvent)
键盘输入 KeyListener keyPressed(KeyEvent)
keyReleased(KeyEvent)
keyTyped(KeyEvent)
部件收到或失去焦点 FocusListener focusGained(FocusEvent)
focusLost(FocusEvent)
部件移动,缩放,显示/隐藏 ComponentListener componentMoved(ComponentEvent)
componentHidden(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
窗口事件 WindowListener windowClosing(WindowEvent)
windowOpened(WindowEvent)
windowIconified(WindowEvent)
windowDeiconified(WindowEvent)
windowClosed(WindowEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
容器增加/删除部件 ContainerListener componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)

在事件处理代码中区分事件源

  • 一个事件源可以注册多个监听者,一个监听者也可以监视多个事件源,在事件处理代码中如何区分事件源呢?不同类型的事件提供了不同类型的方法来区分事件源对象,例如,ActionEvent类中提供了如下两个方法:
    • getSourse()方法:用来获取事件源的对象引用,该方法从父类EventObject继承而来,其余各类事件对象也可用getSourse()方法得到事件源的对象引用。
    • getActionCommand()方法:用来获取按钮事件对象的命令名,按钮对象的命令名默认就是按钮的标签名称,当两个也可以设置不同名称。
  • 按钮对象提供了如下方法分别用来设置和获取按钮的标签与命令名:
    • void setLabel(String label):设置按钮的标签。
    • String getLabel():返回按钮的标签内容。
    • void setActionCommand(String command):设置按钮的命令名。
    • String getActionCommand():返回按钮的命令名。

使用事件适配器类

  • 从上表可以看出,不少事件的监听者接口中定义了多个方法,而程序员往往只关心其中的一两个方法。
  • 为符合接口的实现要求,必须将其他方法写出来并为其提供空的方法体。
  • 为此,Java中为那些具有多个方法的监听者接口提供了事件适配器类,这个类通常命名为XxxAdapter,在该类中以空方法体实现了相应接口的所有方法。
  • 例如,窗体的监听者接口中有7个方法,每个方法对应有WindowEvent类中的一个具体事件,他们是WINDOW_ACTIVATED,WINDOW_DEACTICATED,WINDOW_OPENED,WINDOW_CLOSED,WINDOW_CLOSING,WINDOW_ICONIFIED,WINDOW_DEICONIFIED,类似于动作事件中的getSourse()方法,在WindowEnent类中也有如下方法用于获取引发WindowEvent事件的具体窗口对象。
1
public Window getWindow();
  • 以下结合窗体关闭为例介绍窗体事件适配器类的使用。

[例11-2]:处理窗体的关闭。

  • 关闭窗体通常可考虑如下几种操作方式:
    • 在窗体中安排一个"关闭”按钮,单击按钮关闭窗体。
    • 响应WINDOW_CLOSING事件,单击窗体的"关闭”图标将引发该事件。
    • 使用菜单命令实现关闭,相应菜单的动作事件。
  • 无论哪种均要调用窗体对象的dispose()方法来实现窗体的关闭。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MyFrame extends Frame implements ActionListener{
Button btn;

public MyFrame(){
super("测试窗体关闭");
btn=new Button("关闭");
setLayout(new FlowLayout());
add(btn);
btn.addActionListener(this);
addWindowFocusListener(new closeWin());
setSize(300,200);
setVisible(true);
}

public void actionPerformed(ActionEvent e){
// 判断动作事件的事件源需要将按钮的命令名与"关闭”字符串进行相等比较,采用String类的equals()方法进行比较。
if (e.getActionCommand().equals("关闭")){
dispose();
}
}

public static void main(String[] args) {
new MyFrame();
}
}

class closeWin extends WindowAdapter{
public void windowClosing(WindowEvent e){
// 通过WindowEvent对象的getWindow()方法得到要处理的窗体对象。
Window w = e.getWindow();
w.dispose();
}
}
  • 本程序对监听者的编程用到了两种方式,处理"关闭”按钮事件的监听者是通过实现接口的方式,而处理窗体的"关闭”事件的监听者则采用继承WindowAdapter的方式。

  • closeWin类的windowClosing()方法中,,也可以采用getSource()方法得到事件源对象,但必须用如下形式的强制转换将其转换为Frame或Window对象:

    1
    Frame frm = (Frame)(e.getSourse());
  • closeWin类没有设计为内嵌类,如果将其改为MyFrame的内嵌类,则可省略获取窗体对象的代码,就可直接写dispose()方法。

鼠标事件

  • 鼠标事件共有7种情形,用MouseEvent类的静态整形常量表示,分别是MOUSE_DRAGGED,MOUSE_ENTERED,MOUSE_EXITED,MOUSE_MOVED,MOUSE_PRESSED,MOUSE_RELEASED,MOUSE_CLICKED
  • 鼠标事件的处理通过MouseListenerMouseMotionListener两个接口来描述。
    • MouseListener负责接收和处理鼠标的Press(按下),release(释放),click(单击),enter(移入)和exit(移出)动作触发的事件。
    • MouseMotionListener负责接收和处理鼠标的move(移动)和drag(拖动)动作触发的事件,具体应用中对哪种鼠标事件关心,就在相应的事件处理方法中编写代码。
  • 以下为MouseEvent类的主要方法:
    • public int getX():返回发生鼠标事件的X坐标。
    • public int getY():返回发生鼠标事件的Y坐标。
    • public Point getPoint():返回Point对象,也即鼠标事件发生的坐标点。
    • public int getClickCount():返回鼠标单击事件的连击次数。

高级语义事件和低级语义事件

  • 在图形界面上进行各类鼠标操作均会导致鼠标事件,它具有更广泛发生性,我们将这类事件称为低级语义事件,例如,在按钮上单击,移动,拖动鼠标均会导致鼠标事件。
  • 按钮上的动作事件,则局限于在按钮上单击鼠标才会发生,成为高级语义事件,程序中要关注处理相应事件,必须注册监听者,如果程序对同一操作引发的两类事件均关注,则低级语义事件将先于高级语义事件进行处理。
  • 常见的低级语义事件如下:
    • 组件事件(ComponentEvent):组件尺寸的变化,移动。
    • 容器事件(ContainerEvent):容器中组件的增加,删除。
    • 窗口事件(WindowEvent):关闭窗口,图标化。
    • 焦点事件(FocusEvent):焦点的获得和丢失。
    • 键盘事件(KeyEvent):键按下,释放。
    • 鼠标事件(MouseEvent):鼠标单击,移动等。
  • 高级事件依赖于触发相应事件的图形部件,如在文本框中按回车键会触发ActionEvent事件,单击按钮会触发ActionEcent事件,活动滚动条会触发AdjustmentEvent事件,选中项目列表中的某一项就会触发ItemEvent事件等。

[例11-8]:围棋对弈界面设计。

  • 在窗体中要同时安排棋盘和下棋过程中的若干操作按钮,棋盘用一个Canvas部件绘制,下棋过程控制按钮则部署在一个面板上,棋盘上的棋子信息通过一个二维数组记录,数组元素为1表示黑棋,2表示白棋,0表示无棋子。
  • 为了方便下棋定位操作,在棋盘上绘制一个红色小方框代表位置的小游标,鼠标移动时小游标也跟随移动,小游标移动时要擦除先前的绘制,所以,小游标采用异或方式绘制,擦除只要将其按相同颜色重绘一次既可。
  • 我们可以利用鼠标移动事件处理小游标的移动处理,利用鼠标单击事件处理新下棋子的绘制处理,另外,棋盘及棋子信息利用paint()方法实现绘制。
  • 程序中引入cx和cy两个变量代表游标位置,引入变量player表示当前轮到黑白谁下子,为了实现棋盘位置和大小调整方便,引入实例变量sx,sy记录棋盘左上角的位置信息,并引入实例变量w代表棋盘格子宽度,绘制的棋子宽度比个字宽度略小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class chessGame extends Frame {

chessBoard b = new chessBoard();

public chessGame() {
setBackground(Color.lightGray);
setLayout(new BorderLayout());
add("Center", b);
Panel p = new Panel();
Button pass = new Button("放弃一手");
Button color = new Button("改变棋盘背景");
Button fail = new Button("认输");
Button back = new Button("悔棋");
p.setLayout(new GridLayout(8, 1, 10, 10));
p.add(new Label());// 为界面美观插入一个空标签。
p.add(pass);
p.add(color);
p.add(fail);
p.add(back);
add("East", p);
setSize(500, 400);
setVisible(true);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}

public static void main(String[] args) {
new chessGame();
}
}

class chessBoard extends Canvas {

int[][] chess = new int[19][19];// 存放棋子的状态。
int sx = 20, sy = 20;// 键盘左上角的位置。
int w = 20;// 棋盘每个格子宽度。
int cx = 50;// 下棋位置游标的初值,对应鼠标移动位置。
int cy = 50;
int player = 1;//1表示轮黑下子,0表示轮白下子。

public chessBoard() {
// 处理鼠标移动事件,解决鼠标位置跟踪问题。
this.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
Graphics g = getGraphics();
g.setXORMode(chessBoard.this.getBackground());
g.setColor(Color.red);
g.fillRect(cx - w / 4, cy - w / 4, w / 2, w / 2);
cx = sx + (e.getX() - sx + w / 2) / w * w;
cy = sy + (e.getY() - sx + w / 2) / w * w;
g.fillRect(cx - w / 4, cy - w / 4, w / 2, w / 2);
}
});
// 处理鼠标单击事件,解决在当前游标位置下一个棋子的显示和记录问题。
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {// 鼠标单击表示下子。
Graphics g = getGraphics();
if (chess[(cx - sx) / w][(cy - sy) / w] == 0) {// 是否已有棋子。
if (player == 1) {
g.setColor(Color.BLACK);// 黑棋。
chess[(cx - sx) / w][(cy - sy) / w] = 1;
} else {
g.setColor(Color.white);// 白棋。
chess[(cx - sx) / w][(cy - sy) / w] = 2;
}
g.fillOval(cx - w / 2 + 1, cy - w / 2 + 1, w - 2, w - 2);
player = (player + 1) % 2;// 黑白方轮流下子。
g.setXORMode(chessBoard.this.getBackground());
g.setColor(Color.red);// 用异或方式绘制小游标。
g.fillRect(cx - w / 4, cy - w / 4, w / 2, w / 2);
}
}
});
}

public void paint(Graphics g) {
for (int k = 0; k < 19; k++)// 绘制棋盘。
g.drawLine(sx, sy + k * w, sx + w * 18, sy + k * w);
for (int k = 0; k < 19; k++)
g.drawLine(sx + k * w, sy, sx + k * w, sy + w * 18);
for (int i = 0; i < chess.length; i++)// 绘制棋盘上所有棋子。
for (int j = 0; i < chess[0].length; j++) {
if (chess[i][j] == 1) {
g.setColor(Color.BLACK);
g.fillOval(sx + i * w - w / 2 + 1, sx + j * w - w / 2 + 1, w - 2, w - 2);
} else if (chess[i][j] == 2) {
g.setColor(Color.white);
g.fillOval(sx + i * w - w / 2 + 1, sx + j * w - w / 2 + 1, w - 2, w - 2);
}
}
g.setXORMode(this.getBackground());// 用异或方式绘制小游标。
g.setColor(Color.red);
g.fillRect(cx - w / 4, cy - w / 4, w / 2, w / 2);
}
}
  • 通过getGraphics()方法获取画笔进行图形绘制,而不是调用repaint()方法实现绘制,其好处是避免画面闪烁。

键盘事件

  • 键盘事件包含3个,分别对应KeyEvent类的几个同名的静态整形常量KEY_PRESSED,KEY_RELEASED,KEY_TYPED

  • 相应的,与KeyEvent事件相对应的监听者接口是KeyListener,其中包括3个键盘事件对应的抽象方法:

    • public void keyPressed(KeyEvent e):某个键按下时执行。

    • public void keyReleased(KeyEvent e):某个键被释放时执行。

      public void keyTyped(KeyEvent e):KeyTyped包含KeyPressedKeyRelased两个动作,按键被敲击。

[例11-9]:实现可变色小方框的移动及变色,通过键盘的方向键也可控制小方框的移动,通过字母键B,G等可更改小方框的颜色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class KeyboardDemo extends Frame implements KeyListener {
static final int SQUARE_SIZE = 20;// 小方框的边长。
Color squareColor;// 小方框的颜色。
int squareTop, squareLeft;// 小方框的左上角坐标。

public KeyboardDemo() {
squareTop = 100;// 初始小方框位置。
squareLeft = 100;
squareColor = Color.red;// 初始颜色设置为红色。
addKeyListener(this);// 注册键盘事件监听。
repaint();
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}

public void paint(Graphics g) {
g.setColor(squareColor);
g.fillRect(squareLeft, squareTop, SQUARE_SIZE, SQUARE_SIZE);
}

// 用键盘控制小方块颜色的改变。
// 按输入字符更改小方框颜色的处理。
public void keyTyped(KeyEvent evt) {
char ch = evt.getKeyChar();// 获取输入字符。
if (ch == 'B' || ch == 'b') {
squareColor = Color.blue;
repaint();
} else if (ch == 'G' || ch == 'g') {
squareColor = Color.GREEN;
repaint();
}
}

// 用键盘控制小方块的移动。
public void keyPressed(KeyEvent evt) {
int key = evt.getKeyCode();// 获取按键的编码。
if (key == KeyEvent.VK_LEFT) {// 按键为左箭头。
squareLeft -= 8;
if (squareLeft < 3)
squareLeft = 3;
repaint();
} else if (key == KeyEvent.VK_RIGHT) {// 按键为右箭头。
squareLeft += 8;
if (squareLeft > getWidth() - 3 - SQUARE_SIZE)
squareLeft = getWidth() - 3 - SQUARE_SIZE;
repaint();
} else if (key == KeyEvent.VK_UP) {// 按键为上箭头。
squareTop -= 8;
if (squareTop < 23)
squareTop = 23;
repaint();
} else if (key == KeyEvent.VK_DOWN) {// 按键为下箭头。
squareTop += 8;
if (squareTop > getHeight() - 3 - SQUARE_SIZE)
squareTop = getHeight() - 3 - SQUARE_SIZE;
repaint();
}
}

public void keyReleased(KeyEvent evt) {
}

public static void main(String[] args) {
Frame x = new KeyboardDemo();
x.setSize(300, 300);
x.setVisible(true);
}
}
  • 注意:可以将本程序的keyTyped代码放到keyPressed()方法中,程序运行结果一样,但不能将keyPressed代码放到keyTyped()中,这是因为各种控制键按下时,不产生ketTyped事件,所以,对控制键的编程用keyPressed()keyReleased()方法,而字符键按下时则3个方法均会执行,可选择一个方法进行处理。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!