Swing 以 InputMap/ActionMap 取代 JDK 1.2 的 registerKeyboardAction,提供統一、靈活的鍵盤事件處理機制。本文深入解析三層焦點條件、父子映射鏈、Action 介面實作與完整範例程式。
KeyStroke → Object (通常為 String 鍵值)Object 鍵值 → Action 行為物件流程:按鍵 → KeyStroke → 查找對應 InputMap → 取得鍵值 → 查找 ActionMap → 執行 actionPerformed。
每個 JComponent 擁有三個預設 InputMap,透過 getInputMap(int condition) 取得:
| 常數 | 條件值 | 觸發時機 |
|---|---|---|
| 0 | 組件本身擁有焦點 | |
| 1 | 組件祖先擁有焦點 | |
| 2 | 組件所在視窗擁有焦點 |
單一 ActionMap 由 getActionMap() 取得,三個 InputMap 共用。
InputMap 與 ActionMap 均支援 setParent(InputMap/ActionMap):
Action 延伸 ActionListener,核心方法:
void actionPerformed(ActionEvent e) // 必須實作:具體行為邏輯
void setEnabled(boolean b) / boolean isEnabled() // 啟用狀態
Object getValue(String key) / void putValue(String key, Object value) // 屬性存取
void addPropertyChangeListener(PropertyChangeListener l) // 屬性變更通知
實務上繼承 AbstractAction 僅需覆寫 actionPerformed。
class TextAction extends AbstractAction {
private String prompt;
TextAction(String prompt) { this.prompt = prompt; }
public void actionPerformed(ActionEvent e) {
System.out.println("command=" + e.getActionCommand() + ", prompt=" + prompt);
}
}
TextAction focusSon = new TextAction("focus son");
TextAction focusFather = new TextAction("focus father");
TextAction ancestorAct = new TextAction("ancestor");
TextAction windowAct = new TextAction("window");
JPanel parentPanel = new JPanel();
JPanel sonPanel = new JPanel();
JButton sonBtn = new JButton("son");
JButton parentBtn = new JButton("parent");
parentPanel.add(sonPanel);
sonPanel.add(sonBtn);
parentPanel.add(parentBtn);
frame.add(parentPanel); // 另加入其他按鈕於 Frame 直接層級
InputMap focusIm = sonBtn.getInputMap(WHEN_FOCUSED);
InputMap focusFatherIm = new InputMap();
focusIm.setParent(focusFatherIm); // 保護層:防止核心綁定被覆寫
InputMap ancestorIm = sonBtn.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
InputMap windowIm = sonBtn.getInputMap(WHEN_IN_FOCUSED_WINDOW);
focusIm.put(KeyStroke.getKeyStroke('f'), "actionFocusSon");
focusFatherIm.put(KeyStroke.getKeyStroke('F'), "actionFocusFather");
ancestorIm.put(KeyStroke.getKeyStroke('a'), "actionAncestor");
windowIm.put(KeyStroke.getKeyStroke('w'), "actionWindow");
ActionMap am = sonBtn.getActionMap();
am.put("actionFocusSon", focusSon);
am.put("actionFocusFather", focusFather);
am.put("actionAncestor", ancestorAct);
am.put("actionWindow", windowAct);
| 焦點位置 | 有效按鍵 | 說明 |
|---|---|---|
| sonBtn | f, F, a, w | 三層 InputMap 皆有效 |
| parentBtn | w | 非 sonBtn 祖先,僅視窗層級有效 |
| Frame 上其他按鈕 | w | 同 parentBtn,僅視窗層級有效 |
WHEN_IN_FOCUSED_WINDOW InputMap 設定,整個視窗生效Action.setEnabled(false) 可動態禁用快捷鍵,無需移除綁定getKeyStroke("ctrl shift X")、getKeyStroke(KeyEvent.VK_F1, 0) 等多種建構方式掌握 InputMap/ActionMap 架構,即可構建清晰、可維護、符合平台慣例的鍵盤交互體驗。