0

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 trong keyPressed / 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 trong keyTyped) Trả về ký tự chữ cái char (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:

  1. 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").

  2. 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:

  1. Dùng KeyListener trên JTextArea để cấm gõ các ký tự đặc biệt (như @, #, $).

  2. Dùng Key Bindings trên toàn bộ cửa sổ để bắt phím Ctrl + S (Lưu thông báo) và phím Escape (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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí