Series Java Swing Bài 27: KeyListener & Key Bindings - Bắt sự kiện bàn phím và tạo phím tắt
Việc xử lý bàn phím trong Java Swing được chia làm hai trường phái rõ rệt: Bắt sự kiện cục bộ (người dùng đang gõ văn bản vào một ô cụ thể) và Phím tắt toàn cục (người dùng bấm Ctrl+S ở bất kỳ đâu trên cửa sổ để lưu). Chúng ta sẽ dùng KeyListener cho trường hợp 1 và Key Bindings cho trường hợp 2.
1. KeyListener: Lắng nghe từng phím bấm
KeyListener là một Interface quản lý 3 trạng thái của một thao tác gõ phím. Giống như chuột, bạn có thể dùng KeyAdapter để chỉ ghi đè (override) hàm nào bạn thực sự cần.
-
keyPressed: Xảy ra ngay khoảnh khắc bạn ĐÈ một phím vật lý xuống. Bắt được mọi phím (bao gồm cả Shift, Ctrl, F1, các phím mũi tên). -
keyReleased: Xảy ra khi bạn NHẢ phím đó ra. -
keyTyped: Xảy ra sau khi tổ hợp Press + Release tạo ra một ký tự hợp lệ. Hàm này KHÔNG bắt được các phím chức năng (như Shift, F1, Home) vì chúng không tạo ra chữ.
Lấy thông tin phím bị bấm
-
e.getKeyCode(): (Dùng trongkeyPressed/keyReleased) Trả về mã số nguyên (int) của phím vật lý. Swing cung cấp các hằng sốKeyEvent.VK_*để so sánh (Ví dụ:KeyEvent.VK_ENTER,KeyEvent.VK_UP,KeyEvent.VK_A). -
e.getKeyChar(): (Dùng trongkeyTyped) Trả về ký tự chữ cáichar(Ví dụ: 'a', 'A', '1').
Tiêu hủy sự kiện (Chặn gõ phím) bằng e.consume()
Bạn có thể chặn người dùng gõ một ký tự không mong muốn (ví dụ: cấm nhập số vào ô nhập Tên) bằng cách "ăn" (consume) sự kiện đó trước khi nó kịp in lên giao diện.
Java
JTextField txtName = new JTextField();
txtName.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
char c = e.getKeyChar();
// Nếu ký tự là số từ 0 đến 9
if (Character.isDigit(c)) {
e.consume(); // Tiêu hủy! Ký tự này sẽ không được in ra màn hình
Toolkit.getDefaultToolkit().beep(); // Phát tiếng "Ting" cảnh báo
}
}
});
Điểm yếu chí mạng của KeyListener: Lỗi "Focus"
Một Component (ví dụ JPanel) chỉ có thể bắt được KeyListener nếu nó đang giữ Focus (được trỏ chuột vào). Nếu bạn click chuột sang một ô text khác, JPanel sẽ bị mất Focus và KeyListener của nó hoàn toàn vô dụng. Đó là lý do bạn không bao giờ dùng KeyListener để làm phím tắt toàn cục (Hotkeys) cho ứng dụng.
2. Trùm cuối Hotkeys: Key Bindings (InputMap & ActionMap)
Để tạo phím tắt (như Ctrl + S, Escape) hoạt động mọi lúc mọi nơi trên cửa sổ, bất kể con trỏ chuột đang nằm ở đâu, Swing cung cấp cơ chế Key Bindings. Cơ chế này chia làm 2 từ điển:
-
InputMap(Bản đồ phím): Dịch tổ hợp phím (Ví dụ:Ctrl+S) thành một chuỗi mã tên (Ví dụ:"saveAction"). -
ActionMap(Bản đồ hành động): Chứa đoạn code thực thi tương ứng với cái mã"saveAction"đó.
3 bước gán phím tắt tiêu chuẩn:
Java
// 1. Chọn vùng bắt sự kiện (Thường dùng rootPane hoặc JPanel lớn nhất)
JPanel mainPanel = (JPanel) frame.getContentPane();
// 2. Định nghĩa tổ hợp phím vào InputMap
// Định nghĩa phím Ctrl + S
KeyStroke saveStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK);
// WHEN_IN_FOCUSED_WINDOW: Bắt sự kiện bất kể Component nào đang có focus
mainPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(saveStroke, "doSave");
// 3. Gắn logic thực thi vào ActionMap
mainPanel.getActionMap().put("doSave", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Đã lưu dữ liệu! (Ctrl+S)");
}
});
3. Source Code Thực Chiến: Khung soạn thảo tích hợp Hotkey
Đoạn mã sau tích hợp cả hai kỹ thuật:
-
Dùng
KeyListenertrênJTextAreađể cấm gõ các ký tự đặc biệt (như@,#,$). -
Dùng
Key Bindingstrên toàn bộ cửa sổ để bắt phímCtrl + S(Lưu thông báo) và phímEscape(Xóa rỗng khung soạn thảo) bất kể bạn đang thao tác ở đâu.
Java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class KeyListenerDemo extends JFrame {
public KeyListenerDemo() {
setTitle("Làm chủ Bàn phím (Key Events & Hotkeys)");
setSize(500, 350);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
// ==========================================
// 1. JTEXTAREA VÀ KEYLISTENER (CHẶN KÝ TỰ)
// ==========================================
JTextArea textArea = new JTextArea("Hãy thử gõ ký tự đặc biệt (@, #, $) xem có được không?");
textArea.setFont(new Font("Arial", Font.PLAIN, 16));
textArea.setLineWrap(true);
textArea.setMargin(new Insets(10, 10, 10, 10));
// Gắn KeyAdapter để lọc ký tự
textArea.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
char c = e.getKeyChar();
// Bảng mã ASCII: Chặn các ký tự từ ! đến / (bao gồm @, #, $, %, ^...)
String specialChars = "!@#$%^&*()_+={}[]|\\:\";'<>?,./";
if (specialChars.indexOf(c) != -1) {
e.consume(); // Vô hiệu hóa ký tự này
Toolkit.getDefaultToolkit().beep(); // Phát âm thanh cảnh báo
}
}
});
add(new JScrollPane(textArea), BorderLayout.CENTER);
// ==========================================
// 2. KEY BINDINGS (PHÍM TẮT TOÀN CỤC)
// ==========================================
// Lấy InputMap và ActionMap của RootPane (Cấp cao nhất của cửa sổ)
JRootPane rootPane = this.getRootPane();
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = rootPane.getActionMap();
// ---- HOTKEY 1: Ctrl + S (Lưu) ----
KeyStroke saveKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx());
inputMap.put(saveKey, "saveLogic"); // Đăng ký phím vào từ điển
actionMap.put("saveLogic", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(rootPane,
"Đã lưu văn bản bằng phím tắt Ctrl+S",
"Lưu thành công",
JOptionPane.INFORMATION_MESSAGE);
}
});
// ---- HOTKEY 2: Phím ESCAPE (Xóa Form) ----
KeyStroke escKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); // 0 tức là không cần đè Ctrl/Shift
inputMap.put(escKey, "clearLogic");
actionMap.put("clearLogic", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
int confirm = JOptionPane.showConfirmDialog(rootPane,
"Bạn bấm ESC. Có muốn xóa sạch văn bản không?",
"Xác nhận xóa",
JOptionPane.YES_NO_OPTION);
if (confirm == JOptionPane.YES_OPTION) {
textArea.setText("");
}
}
});
// Thêm một JLabel hướng dẫn phía dưới
JLabel lblHelp = new JLabel(" Phím tắt: [Ctrl + S] để Lưu | [ESC] để Xóa rỗng", SwingConstants.CENTER);
lblHelp.setForeground(Color.BLUE);
add(lblHelp, BorderLayout.SOUTH);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new KeyListenerDemo().setVisible(true));
}
}
All rights reserved