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/有效 final 變數的限制。
  • 每次按下按鈕時,遞增計數並更新標籤文字。

練習點子:試著每第五次按下時改變按鈕顏色!

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/有效 final 的變數。 在 Lambda 內只能使用 final 或「有效 final」變數。對於計數器可以使用陣列或專用類別(想更進階可用 AtomicInteger)。

錯誤 №4: 忘記移除監聽器。 如果你移除了元件,卻沒有移除監聽器,可能導致記憶體洩漏。當元件不再需要時,請取消訂閱。

錯誤 №5: 未實作介面中的所有方法。 如果直接實作例如 MouseListener,別忘了所有方法!更好的方式是使用適配器(MouseAdapterKeyAdapter)。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION