首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 鍵盤事件處理:從 registerKeyboardAction 到 InputMap/ActionMap 最佳實務

     編輯:Java 相關知識

前言

在 Swing 應用程式中處理鍵盤事件是常見需求,例如實作熱鍵、遊戲控制或表單快捷鍵。早期常用 registerKeyboardAction,但現已棄用。本文介紹兩種實作方式,並說明為何應改用 InputMap/ActionMap 架構。

方式一:registerKeyboardAction(已棄用)

此方法直接在元件上註冊鍵盤動作,語法簡單但靈活度有限。

程式碼範例

public class KeystrokeTest extends JApplet {
    private JButton button = new JButton("button");
    public void init() {
        Container contentPane = getContentPane();
        JPanel panel = new JPanel();
        JCheckBox checkbox = new JCheckBox("checkbox");
        JButton southButton = new JButton("south button");
        Listener listener = new Listener();
        
        panel.setBorder(BorderFactory.createTitledBorder("Ancestor of button and checkbox"));
        
        // WHEN_FOCUSED:元件擁有焦點時觸發
        checkbox.registerKeyboardAction(listener,
            KeyStroke.getKeyStroke(KeyEvent.VK_F, 0, false),
            JComponent.WHEN_FOCUSED);
        
        // WHEN_ANCESTOR_OF_FOCUSED_COMPONENT:祖先元件擁有焦點時觸發
        panel.registerKeyboardAction(listener,
            KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.ALT_MASK, false),
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        
        // WHEN_IN_FOCUSED_WINDOW:視窗內任一元件擁有焦點時觸發
        southButton.registerKeyboardAction(listener,
            KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), // true 表示 key released
            JComponent.WHEN_IN_FOCUSED_WINDOW);
        
        panel.add(button);
        panel.add(checkbox);
        contentPane.add(panel, "Center");
        contentPane.add(southButton, "South");
    }
}

class Listener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        Object src = e.getSource();
        if (src instanceof JCheckBox) {
            System.out.println("'f' key PRESSED when checkbox had focus");
        } else if (src instanceof JPanel) {
            System.out.println("'ALT-a' key PRESSED when ancestor of titled panel had focus");
        } else if (src instanceof JButton) {
            System.out.println("'w' key RELEASED when any component in window had focus");
        }
        System.out.println("Source: " + src.getClass().getName());
    }
}

三種焦點條件對照

常數觸發時機
WHEN_FOCUSED該元件本身擁有鍵盤焦點
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT該元件的子孫元件擁有焦點
WHEN_IN_FOCUSED_WINDOW視窗內任一元件擁有焦點(全域熱鍵)

方式二:InputMap / ActionMap(推薦標準做法)

這是 Swing 設計的「動作映射」架構,將「按鍵觸發」與「實際動作」解耦,支援更複雜的綁定邏輯。

核心概念

  • InputMap:將 KeyStroke 映射到動作名稱(Object)
  • ActionMap:將動作名稱映射到實際的 Action 物件
  • 三種 InputMap 對應三種焦點條件:getInputMap(WHEN_FOCUSED) 等

程式碼範例:數字鍵盤模擬

public class KeyBindingDemo {
    public static void main(String[] args) {
        try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ignore) {}
        
        final JTextField tf = new JTextField();
        tf.setEnabled(false); // 僅顯示,不接受直接輸入
        
        JPanel p = new JPanel(new GridLayout(0, 3, 5, 5));
        for (int i = 0; i <= 9; i++) {
            final JButton btn = new JButton(String.valueOf(i));
            p.add(btn);
            
            // 1. 定義按鍵觸發條件:數字鍵 0-9,在視窗焦點時
            KeyStroke ks = KeyStroke.getKeyStroke((char)('0' + i));
            String actionName = "PressKeyAction" + i; // 每個按鈕獨立動作名稱
            
            // 2. 綁定按鍵 -> 動作名稱
            btn.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, actionName);
            
            // 3. 綁定動作名稱 -> 實際行為
            btn.getActionMap().put(actionName, new AbstractAction() {
                @Override public void actionPerformed(ActionEvent e) {
                    btn.doClick(); // 程式化點擊按鈕
                }
            });
            
            // 4. 按鈕本身的點擊邏輯
            btn.addActionListener(e -> tf.setText(tf.getText() + btn.getText()));
        }
        
        JFrame f = new JFrame("鍵盤綁定示範");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout(0, 5));
        f.getContentPane().add(p, BorderLayout.CENTER);
        f.getContentPane().add(tf, BorderLayout.NORTH);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

為何 registerKeyboardAction 被棄用?

查看 JDK 原始碼可發現,registerKeyboardAction 內部實際上就是封裝了 InputMap/ActionMap 操作:

public void registerKeyboardAction(ActionListener anAction, String aCommand,
                                   KeyStroke aKeyStroke, int aCondition) {
    InputMap inputMap = getInputMap(aCondition, true);
    if (inputMap != null) {
        ActionMap actionMap = getActionMap(true);
        ActionStandin action = new ActionStandin(anAction, aCommand);
        inputMap.put(aKeyStroke, action);
        if (actionMap != null) actionMap.put(action, action);
    }
}

直接使用 InputMap/ActionMap 好處:

  1. 解耦:按鍵定義與業務邏輯分離,易於維護與擴展
  2. 靈活:可動態啟用/停用綁定、共享 Action 物件、支援複雜組合鍵
  3. 標準:符合 Swing 架構設計,所有現代 Swing 元件皆支援

關於 KeyListener 的迷思

許多初學者傾向使用 addKeyListener,但 不推薦 用於 Swing 應用:

  • 需元件擁有焦點且 isFocusable() 為 true
  • 焦點管理複雜,易導致事件不觸發
  • 無法輕易實作全域熱鍵(WHEN_IN_FOCUSED_WINDOW)

最佳實務: 針對特定元件綁定用 InputMap/ActionMap;全域熱鍵則在頂層容器(如 JRootPane)的 WHEN_IN_FOCUSED_WINDOW InputMap 設定。

總結

特性registerKeyboardActionInputMap/ActionMap
API 狀態已棄用標準推薦
程式碼結構耦合度高關注點分離
動態修改困難容易
Action 重用不支援支援

請直接使用 InputMap/ActionMap 架構,這是 Swing 處理鍵盤事件的正統之道。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved