1. Giới thiệu rất ngắn về Swing và AWT
Nếu Java là một chiếc ô tô, thì Swing và AWT — chính là “bảng điều khiển” và “vô lăng” của nó. AWT (Abstract Window Toolkit) — thư viện đầu tiên để tạo ứng dụng cửa sổ trong Java. Nó sử dụng các thành phần giao diện bản địa của hệ điều hành, nên sẽ hiển thị khác nhau trên Windows, Linux và Mac.
Swing — lớp bao hiện đại hơn trên AWT, được viết hoàn toàn bằng Java. Swing đẹp hơn, linh hoạt hơn, hỗ trợ rất nhiều tính năng bổ sung và trông giống nhau trên mọi nền tảng (gần như vậy). Trong cả hai trường hợp, mọi thành phần giao diện (nút, ô văn bản, checkbox, v.v.) đều hỗ trợ làm việc với sự kiện.
Trong bài giảng này, chúng ta sẽ sử dụng Swing: nó dễ cho người mới và thường gặp trong các ví dụ học tập. Hãy tìm hiểu cách xử lý sự kiện qua nhiệm vụ phổ biến nhất — phản ứng khi nhấn nút.
Tạo nút
Trong Swing, tạo nút như sau:
JButton button = new JButton("Bấm tôi!");
Ở đây “Bấm tôi!” — là nhãn trên nút.
Thêm listener
Để phản ứng với lần nhấn, cần “đăng ký” listener cho sự kiện:
button.addActionListener(new MyActionListener());
Nhưng MyActionListener là gì? Đây là một lớp triển khai interface 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("Nút đã được nhấn!");
}
}
Khi người dùng nhấn nút, Swing gọi phương thức actionPerformed của tất cả listener đã đăng ký qua addActionListener.
Ví dụ hoàn chỉnh: cửa sổ đơn giản với nút
Hãy ghép tất cả lại và tạo một ứng dụng chạy được:
import javax.swing.*;
import java.awt.event.*;
public class SimpleButtonApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Ví dụ về sự kiện");
JButton button = new JButton("Bấm tôi!");
// Thêm listener cho nút
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Nút đã được nhấn!");
JOptionPane.showMessageDialog(frame, "Hoan hô! Nút đã được nhấn!");
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Điều gì xảy ra ở đây:
- Tạo cửa sổ (JFrame).
- Tạo nút (JButton).
- Thêm listener (lớp ẩn danh để khỏi phải tạo file riêng).
- Bên trong actionPerformed, in thông báo ra console và bật hộp thoại (JOptionPane).
- Thêm nút vào cửa sổ và hiển thị cửa sổ cho người dùng.
Hãy tự thử sao chép đoạn mã này, chạy — và bấm nút!
2. Lớp ẩn danh và biểu thức lambda
Trong mã Java hiện đại, viết các lớp riêng chỉ cho một handler — như lái xe tăng đi mua bánh mì. Tiện hơn nhiều khi dùng lớp ẩn danh hoặc biểu thức lambda.
Lớp ẩn danh
Chúng ta đã thấy cách này ở trên:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// mã xử lý
}
});
Ở đây lớp được khai báo ngay tại chỗ, không có tên.
Biểu thức lambda (Java 8+)
Nếu interface listener là chức năng (tức chỉ có một phương thức trừu tượng), bạn có thể dùng lambda:
button.addActionListener(e -> {
System.out.println("Nút được nhấn qua lambda!");
});
Hoặc ngắn hơn nữa nếu thân chỉ một dòng:
button.addActionListener(e -> System.out.println("Lambda: nút đã được nhấn!"));
Điều này không chỉ rút gọn mã mà còn làm nó dễ đọc hơn. Thông tin cho người tò mò: interface ActionListener là interface chức năng, vì nó chỉ có một phương thức actionPerformed.
4. Các loại sự kiện khác
Thế giới không chỉ có mỗi nút! Trong Swing và AWT, hầu như mọi thành phần giao diện đều hỗ trợ các sự kiện riêng. Dưới đây là những loại phổ biến nhất:
Sự kiện chuột: MouseListener và MouseAdapter
Nếu muốn phản ứng với cú nhấp chuột, hãy dùng MouseListener:
button.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Nhấp chuột vào nút!");
}
// Các phương thức còn lại có thể để trống
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
});
Việc viết năm phương thức trống chỉ vì một handler — không tiện. Cho nên có MouseAdapter:
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Nhấp chuột vào nút (qua adapter)!");
}
});
MouseAdapter triển khai tất cả các phương thức của interface, còn bạn chỉ override những cái cần.
Sự kiện bàn phím: KeyListener/KeyAdapter
Nếu cần bắt phím nhấn:
button.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
System.out.println("Phím được nhấn: " + e.getKeyChar());
}
});
Sự kiện thay đổi văn bản: DocumentListener
Đối với trường văn bản (JTextField, JTextArea) có các listener chuyên dụng:
JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
System.out.println("Văn bản đã thay đổi: " + textField.getText());
}
@Override public void removeUpdate(DocumentEvent e) {}
@Override public void changedUpdate(DocumentEvent e) {}
});
Nhân tiện, với hầu hết sự kiện đều có adapter để khỏi phải viết các phương thức trống.
5. Thực hành: ví dụ nhỏ với cửa sổ và nút
Hãy làm một ứng dụng GUI đơn giản nhất: một cửa sổ có nút; mỗi lần nhấn nút sẽ tăng bộ đếm và hiển thị thông báo.
Ví dụ: bộ đếm số lần nhấn
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ClickCounterApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Bộ đếm số lần nhấn");
JButton button = new JButton("Bấm tôi!");
JLabel label = new JLabel("Số lần nhấn: 0");
// Bộ đếm số lần nhấn (phải là final hoặc effectively final)
final int[] count = {0};
button.addActionListener(e -> {
count[0]++;
label.setText("Số lần nhấn: " + 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);
}
}
Giải thích:
- Tạo cửa sổ, nút và nhãn để hiển thị bộ đếm.
- Sử dụng mảng một phần tử (final int[] count = {0};) để vượt qua giới hạn của lambda về biến final/effectively final.
- Mỗi lần bấm nút, tăng bộ đếm và cập nhật văn bản của nhãn.
Gợi ý luyện tập: hãy thử đổi màu nút mỗi lần nhấn thứ năm!
6. Mô hình sự kiện hoạt động bên trong như thế nào
Hãy nhìn nhanh dưới nắp máy. Khi bạn gọi:
button.addActionListener(listener);
nút (đối tượng JButton) thêm listener của bạn vào danh sách nội bộ. Khi người dùng nhấn nút, bên trong diễn ra như sau:
- Nút tạo đối tượng sự kiện (ActionEvent).
- Nút duyệt danh sách listener và gọi phương thức actionPerformed của từng cái.
- Handler của bạn thực hiện các hành động cần thiết (ví dụ, cập nhật nhãn).
Điều quan trọng cần nhớ: mỗi sự kiện có thể có bất kỳ số lượng listener nào — bạn có thể thêm nhiều handler cho một nút, chúng sẽ được gọi lần lượt.
7. Sơ đồ trực quan: xử lý sự kiện
graph TD
A[Người dùng nhấn nút] --> B{JButton}
B --> C[Tạo ActionEvent]
C --> D[Gọi actionPerformed của tất cả listener]
D --> E[Handler thực thi hành động]
7. Các lỗi thường gặp khi làm việc với sự kiện trong Swing và AWT
Lỗi số 1: quên đăng ký listener. Nút không phản hồi khi nhấn vì bạn quên gọi addActionListener.
Lỗi số 2: công việc nặng trong handler. Nếu trong handler sự kiện bạn chạy vòng lặp dài hoặc tải gì đó từ Internet, giao diện sẽ “đóng băng”. Với tác vụ dài, hãy dùng luồng riêng hoặc SwingWorker.
Lỗi số 3: cố thay đổi biến từ lambda mà không final/effectively final. Bên trong lambda, bạn chỉ có thể dùng biến final hoặc “effectively final”. Với bộ đếm, hãy dùng mảng hoặc các lớp chuyên dụng (AtomicInteger, nếu muốn “chuyên nghiệp”).
Lỗi số 4: quên gỡ listener. Nếu bạn gỡ bỏ component nhưng không gỡ listener, có thể rò rỉ bộ nhớ. Hãy hủy đăng ký khi component không còn cần thiết.
Lỗi số 5: không triển khai đầy đủ các phương thức của interface. Nếu triển khai trực tiếp, ví dụ MouseListener, đừng quên tất cả các phương thức! Tốt hơn hãy dùng adapter (MouseAdapter, KeyAdapter).
GO TO FULL VERSION