CodeGym /课程 /JAVA 25 SELF /Swing 和 AWT 的事件:基础与示例

Swing 和 AWT 的事件:基础与示例

JAVA 25 SELF
第 50 级 , 课程 1
可用

1. Swing 和 AWT 的超简入门

如果把 Java 比作汽车,那么 Swing 和 AWT 就是它的“仪表盘”和“方向盘”。AWT (Abstract Window Toolkit) 是 Java 中最早用于创建桌面窗口应用的库。它使用操作系统的原生控件,因此在 Windows、Linux 和 Mac 上的外观各不相同。

Swing 是在 AWT 之上的更现代的封装,完全由 Java 编写。Swing 更美观、更灵活,支持大量额外功能,并且在各平台上看起来基本一致(当然,也不完全一样)。在这两者中,所有界面元素(按钮、文本框、复选框等)都支持事件处理。

在本讲中我们将使用 Swing:对新手更友好,也更常见于教学示例。接下来以最常见的任务——响应按钮点击——为例,来看看事件处理是如何运作的。

创建按钮

在 Swing 中,按钮这样创建:

JButton button = new JButton("点我!");

这里的“点我!”是按钮上的文字。

添加监听器

要对点击做出响应,需要为事件“注册”监听器:

button.addActionListener(new MyActionListener());

那么这个 MyActionListener 是谁?它是一个实现了接口 ActionListener 的类:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyActionListener implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("按钮已被点击!");
    }
}

当用户点击按钮时,Swing 会调用所有通过 addActionListener 注册的监听器的 actionPerformed 方法。

完整示例:带按钮的简单窗口

把这些放在一起,创建一个可运行的应用:

import javax.swing.*;
import java.awt.event.*;

public class SimpleButtonApp {
    public static void main(String[] args) {
        JFrame frame = new JFrame("事件示例");
        JButton button = new JButton("点我!");

        // 给按钮添加监听器
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("按钮已被点击!");
                JOptionPane.showMessageDialog(frame, "太棒了!按钮被按下了!");
            }
        });

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(button);
        frame.setSize(300, 200);
        frame.setVisible(true);
    }
}

这里发生了什么:

  • 创建窗口(JFrame)。
  • 创建按钮(JButton)。
  • 添加监听器(使用匿名类,避免单独建文件)。
  • actionPerformed 中向控制台和弹窗(JOptionPane)输出消息。
  • 把按钮加到窗口并显示给用户。

自己动手试试 复制这段代码、运行——然后点击按钮!

2. 匿名类与 Lambda 表达式

在现代 Java 代码里,为一个处理器单独写一个类,就像开坦克去买面包。更方便的是使用 匿名类Lambda 表达式

匿名类

我们在上面已经见过这种写法:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 处理代码
    }
});

这里的类就地声明,没有名字。

Lambda 表达式(Java 8+)

如果监听器接口是函数式的(也就是只包含一个抽象方法),就可以使用 Lambda:

button.addActionListener(e -> {
    System.out.println("通过 Lambda 点击了按钮!");
});

如果只有一行代码,还可以更简洁:

button.addActionListener(e -> System.out.println("Lambda:按钮被点击!"));

这不仅能让代码更短,也更易读。冷知识:接口 ActionListener 是函数式接口,因为它只有一个方法 actionPerformed

4. 其他事件类型

世界不止有按钮!在 Swing 和 AWT 中,几乎每个界面元素都有自己的事件。常见的有:

鼠标事件:MouseListener 和 MouseAdapter

如果需要响应鼠标点击,可以使用 MouseListener

button.addMouseListener(new MouseListener() {
    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("鼠标点击了按钮!");
    }
    // 其余方法可以留空
    @Override public void mousePressed(MouseEvent e) {}
    @Override public void mouseReleased(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e) {}
    @Override public void mouseExited(MouseEvent e) {}
});

为了一个处理器写五个空方法并不方便。此时可以用 MouseAdapter

button.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("鼠标点击了按钮(通过适配器)!");
    }
});

MouseAdapter 已经实现了接口中的所有方法,你只需重写需要的那些。

键盘事件:KeyListener/KeyAdapter

如果需要捕获按键:

button.addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
        System.out.println("按下的键:" + e.getKeyChar());
    }
});

文本变化事件:DocumentListener

对于文本输入组件(JTextFieldJTextArea)有专门的监听器:

JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentListener() {
    @Override
    public void insertUpdate(DocumentEvent e) {
        System.out.println("文本已改变:" + textField.getText());
    }
    @Override public void removeUpdate(DocumentEvent e) {}
    @Override public void changedUpdate(DocumentEvent e) {}
});

顺便说一句,多数事件都有相应的适配器类,可以避免书写空方法.

5. 实战:带窗口和按钮的迷你示例

我们来做一个最简单的 GUI 应用:一个带按钮的窗口,点击按钮会递增计数并显示消息。

示例:点击计数器

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ClickCounterApp {
    public static void main(String[] args) {
        JFrame frame = new JFrame("点击计数器");
        JButton button = new JButton("点我!");
        JLabel label = new JLabel("点击次数:0");
        // 点击计数器(必须是 final 或 effectively final)
        final int[] count = {0};

        button.addActionListener(e -> {
            count[0]++;
            label.setText("点击次数:" + count[0]);
        });

        frame.setLayout(new FlowLayout());
        frame.add(button);
        frame.add(label);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(250, 100);
        frame.setVisible(true);
    }
}

说明:

  • 创建窗口、按钮和用于显示计数的标签。
  • 使用一个单元素数组(final int[] count = {0};),以绕过 Lambda 对 final/effectively final 变量的限制。
  • 每次点击按钮时递增计数并更新标签文本。

练习点子:试着在每第5次点击时改变按钮颜色!

6. 事件模型在内部如何运作

让我们稍微看看底层。当你调用:

button.addActionListener(listener);

按钮(JButton 对象)会把你的监听器加入内部列表。 当用户点击按钮时,内部会发生以下步骤:

  1. 按钮创建事件对象(ActionEvent)。
  2. 按钮遍历监听器列表并调用每个监听器的 actionPerformed 方法。
  3. 你的处理器执行相应动作(例如更新标签)。

要点:每个事件都可以有任意数量的监听器——你可以为同一按钮添加多个处理器,它们会依次被调用。

7. 可视化示意:事件处理流程

graph TD
    A[用户点击按钮] --> B{JButton}
    B --> C[创建 ActionEvent]
    C --> D[调用所有监听器的 actionPerformed]
    D --> E[处理器执行操作]

7. 在 Swing 和 AWT 处理中常见的错误

错误 1:忘记注册监听器。 按钮不响应点击,因为忘记调用 addActionListener

错误 2:在处理器里做重活。 如果在事件处理器中执行耗时循环或下载网络资源,界面会“卡住”。对于耗时任务请使用单独的线程或 SwingWorker

错误 3:在 Lambda 中修改非 final/effectively final 的变量。 在 Lambda 中只能使用 final 或“effectively final”的变量。对于计数器可以使用数组或专用类(想更专业些可用 AtomicInteger)。

错误 4:忘记移除监听器。 如果删除组件但没有移除其监听器,可能造成内存泄漏。当组件不再需要时,请取消订阅。

错误 5:接口的方法未全部实现。 如果直接实现例如 MouseListener,别忘了实现所有方法!更好的做法是使用适配器(MouseAdapterKeyAdapter)。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION