Swing包提供了一種非常實用的機制來封裝命令,並將它們連接到多個事件源,這就是Action接口。一個動作是一個封裝下列內容的對象:
× 命令的說明(一個文本字符串和一個可選圖標);
× 執行命令所需要的參數(例如,在列舉的例子中請求改變的顏色)。
Action接口包含下列內容:
- public void actionPerformed(ActionEvent e);
- public void setEnabled(boolean b);
- public boolean isEnabled();
- public void putValue(String key, Object value);
- public Object getValue(String key);
- public void addPropertyChangeListener(PropertyChangeListener listener);
- public void removePropertyChangeListener(PropertyChangeListener listener);
actionPerformed方法是ActionListener接口中的一個:實際上,Action接口擴展於ActionListener接口,因此,可以在任何需要ActionListener對象的地方使用Action對象。
setEnabled和isEnbaled兩個方法允許啟用或禁用這個動作,並檢查這個動作當前是否啟用。當一個連接到菜單或工具欄上的動作被禁用時,這個選項就會變成灰色。
putValue和getValue兩個方法允許存儲和檢索動作對象中的任意名/值。有兩個重要的預定義字符串:Action.NAME和Action.SMALL_ICON,用於將動作的名字和圖標存儲到一個動作對象中:
- action.putValue(Action.NAME,"Blue");
- action.putValue(Action.SMALL_ICON,new ImageIcon("blue-ball.gif"));
表1給出了所有預定義的動作表名稱。
名稱 | 值 |
---|---|
NAME | 動作名稱,顯示在按鈕和菜單上 |
SMALL_ICON | 存儲小圖標的地方;顯示在按鈕、菜單項或工具欄中 |
SHORT_DESCRIPTION | 圖標的簡要說明;顯示在工具提示中 |
LONG_DESCRIPTION | 圖標的詳細說明;使用在在線幫助中。沒有Swing組件使用這個值 |
MNEMONIC_KEY | 快捷鍵縮寫;顯示在菜單項中 |
ACCELERATOR_KEY | 存儲加速擊鍵的地方;Swing組件不使用這個值 |
ACTION_COMMAND_KEY | 歷史遺留;僅在舊版本的registerKeyboardAction方法中使用 |
DEFALUT | 常用的綜合屬性;Swing組件不使用這個值 |
如果動作對象添加到菜單或工具欄上,它的名稱和圖標就會被自動地提取出來,並顯示在菜單項或工具欄項中。SHORT_DESCRIPTION值變成了工具提示。
addPropertyChangeListener和removePropertyChangeListener兩個方法能夠讓其他對象在動作對象的屬性發生變化時得到通告,尤其是菜單或工具欄觸發的動作。例如,如果增加一個菜單,作為動作對象的屬性變更監聽器,而這個動作對象愛你個隨后被禁用,菜單就會被調用,並將動作名稱變為灰色。屬性變更監聽器是一種常用的構造形式,它是JavaBeans組件模型的一部分。
需要注意,Action是一個接口,而不是一個類。實現這個接口的所有類都必須實現剛才討論的7個方法。慶幸的是,有一個類實現了這個接口除actionPerformed方法之外的所有方法,它就是AbstractAction。這個類存儲了所有名/值對,並管理着屬性變更監聽器。我們可以直接擴展AbstractAction類,並在擴展類中實現actionPerformed方法。
下面構造一個用於執行改變顏色命令的動作對象。首先存儲這個命令的名稱、圖標和需要的顏色。將顏色存儲在AbstractAction類提供的名/值對表中。下面是ColorAction類的代碼。構造器設置名/值對,而actionPerformed方法執行改變顏色的動作。
- public class ColorAction extends AbstractAction
- {
- public ColorAction(String name,Icon icon,Color c)
- {
- putValue(Action.NAME,name);
- putValue(Action.SMALL_ICON,icon);
- putValue(Action.SHORT_DESCRIPTION,"Set panel color to "+name.toLowerCase());
- putValue("color",c);
- }
- public void actionPerformed(ActionEvent event)
- {
- Color c = (Color)getValue("color");
- buttonPanel.setBackground(c);
- }
- }
在測試程序中,創建了三個這個類的對象,如下所示:
- Action yellowAction = new ColorAction("Yellow",new ImageIcon("yellow-ball.gif"),Color.YELLOW);
- Action blueAction = new ColorAction("Blue",new ImageIcon("blue-ball.gif"),Color.BLUE);
- Action redAction = new ColorAction("Red",new ImageIcon("red-ball.gif"),Color.RED);
接下來,將這個動作與一個按鈕關聯起來。由於JButton有一個用Action對象作為參數的構造器,所以實現這項操作很容易。
- buttonPanel.add(new JButton(yellowAction));
- buttonPanel.add(new JButton(blueAction));
- buttonPanel.add(new JButton(redAction));
構造器讀取動作的名稱和圖標,為工具提示設置簡要說明,將動作設置為監聽器。
最后,想要將這個動作對象添加到擊鍵中,以便讓用戶敲擊鍵盤命令來執行這項動作。為了將動作與擊鍵關聯起來,首先需要生成KeyStroke類對象。這是一個很有用的類,它封裝了對鍵的說明。要想生成一個KeyStroke對象,不要調用構造器,而是調用KeyStroke類中的靜態getKeyStroke方法:
- KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrol B");
為了能夠理解下一個步驟,需要知道Keyborad focus的概念。用戶界面中可以包含許多按鈕、菜單、滾動欄以及其他的組件。當用戶敲擊鍵盤時,這個動作會被發送給擁有焦點的組件。通常具有焦點的組件可以明顯地察覺到(但並不總是這樣),例如,在Java觀感中,具有焦點的按鈕在按鈕文本周圍有一個細的矩形邊框。用戶可以使用TAB鍵在組件之間移動焦點。當按下SPACE鍵時,就點擊了擁有焦點的按鈕。還有一些鍵執行一些其他的動作,例如,按下箭頭鍵可以移動滾動條。
然而,在這里的示例中,並不希望將擊鍵發送給擁有焦點的組件。否則,每個按鈕都需要知道如何處理CTRL+Y、CTRL+B和CTRL+R這些組合鍵。
這是一個常見的問題,Swing設計者給出了一種很便捷的解決方案。每個JComponent有三個輸入映射(input maps),每一個映射的KeyStroke對象都與動作關聯。三個輸入映射對應着三個不同的條件。
標志 | 激活條件 |
---|---|
WHEN_FOCUSED | 當這個組件擁有鍵盤焦點時 |
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT | 當這個組件包含了擁有鍵盤焦點的組件時 |
WHEN_IN_FOCUSED_WINDOW | 當這個組件被包含在一個擁有鍵盤焦點組件的窗口中時 |
擊鍵處理將按照下列順序檢查這些映射:
1)檢查具有輸入焦點組件的WHEN_FOCUSED映射。如果這個擊鍵存在,將執行對應的動作。如果動作已啟用,則停止處理。
2)從具有輸入焦點的組件開始,檢查其父組件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT映射。一旦找到擊鍵對應的映射,就執行對應的動作。如果動作已啟用,將停止處理。
3)查看具有輸入焦點的窗口中的所有可視的和啟用的組件,這個擊鍵被注冊到WHEN_IN_FOCUSED_WINDOW映射中。給這些組件(按照擊鍵注冊的順序)一個執行對應動作的機會。一旦第一個啟用的動作被執行,就停止處理。如果一個擊鍵在多個WHEN_IN_FOCUSED_WINDOW映射中出現,這部分處理就可能會出現問題。
可以使用getInputMap方法從組件中得到輸入映射。例如:
- InputMap imap = buttonPanel.getInputMap(JComponent.WHEN_FOCUSED);
WHEN_FOCUSED條件意味着在當前組件擁有鍵盤焦點時會查看這個映射。在這里的示例中,並不想使用這個映射。是某個按鈕擁有輸入焦點,而不是面板。其他的兩個映射都能夠很好地完成增加顏色改變擊鍵的任務。在示例程序中使用的是WHEN_ANCESTOR_OF_FOCUSED_COMPONENT。
InputMap不能直接地將KeyStroke對象映射到Action對象。而是先映射到任意對象上,然后由ActionMap類實現將對象映射到動作上的第2個映射。這樣很容易實現來自不同輸入映射的擊鍵共享一個動作的目地。
因而,每個組件都可以有三個輸入映射和一個動作映射。為了將它們組合起來,需要為動作命名。下面是將鍵與動作關聯起來的方式:
- imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
- ActionMap amap = buttonPanel.getActionMap();
- amap.put("panel.yellow",yellowAction);
習慣上,使用字符串none表示空動作。這樣可以輕松地取消一個鍵動作:
- imap.put(KeyStroke.getKeyStroke("ctrl C"),"none");
警告:JDK文檔提倡使用動作名作為動作鍵。我們並不認為這是一個好建議。在按鈕和菜單項上顯示的動作名,UI設計者可以隨心所欲地進行更改,也可以將其翻譯成多種語言。使用這種不牢靠的字符串作為查詢鍵不是一種好的選擇。建議將動作名與現實的名字分開。
下面總結一下用同一個動作響應按鈕、菜單項或擊鍵的方式:
1)實現一個擴展於AbstractAction類的類。多個相關的動作可以使用同一個類。
2)構造一個動作類的對象。
3)使用動作對象創建按鈕或菜單項。構造器將從動作對象中讀取標簽文本和圖標。
4)為了能夠通過擊鍵觸發動作,必須額外地執行幾步操作。首先定位頂層窗口組件,例如,包含所有其他組件的面板。
5)然后,得到頂層組件的WHEN_ANCESTOR_OF_FOCUS_COMPONENT輸入映射。為需要的擊鍵創建一個KeyStrike對象。創建一個描述動作字符串這樣的動作鍵對象。將(擊鍵、動作鍵)對添加到輸入映射中。
6)最后,得到頂層組件的動作映射。將(動作鍵,動作對象)添加到映射中。
將按鈕和擊鍵映射到動作對象的完整程序代碼如下:
- import java.awt.*;
- import java.awt.event.*;
- import javax.swing.*;
- public class ActionTest {
- public static void main(String[] args) {
- EventQueue.invokeLater(new Runnable() {
- public void run() {
- ActionFrame frame = new ActionFrame();
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setVisible(true);
- }
- });
- }
- }
- class ActionFrame extends JFrame {
- public ActionFrame() {
- setTitle("ActionTest");
- setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
- buttonPanel = new JPanel();
- // define actions
- Action yellowAction = new ColorAction("Yellow", new ImageIcon("yellow-ball.gif"), Color.YELLOW);
- Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE);
- Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED);
- // add buttons for these actions
- buttonPanel.add(new JButton(yellowAction));
- buttonPanel.add(new JButton(blueAction));
- buttonPanel.add(new JButton(redAction));
- // add panel to frame
- add(buttonPanel);
- // associate the Y, B, and R keys with names
- InputMap imap = buttonPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
- imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
- imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue");
- imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");
- // associate the names with actions
- ActionMap amap = buttonPanel.getActionMap();
- amap.put("panel.yellow", yellowAction);
- amap.put("panel.blue", blueAction);
- amap.put("panel.red", redAction);
- }
- public class ColorAction extends AbstractAction {
- public ColorAction(String name, Icon icon, Color c) {
- putValue(Action.NAME, name);
- putValue(Action.SMALL_ICON, icon);
- putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase());
- putValue("color", c);
- }
- public void actionPerformed(ActionEvent event) {
- Color c = (Color) getValue("color");
- buttonPanel.setBackground(c);
- }
- }
- private JPanel buttonPanel;
- public static final int DEFAULT_WIDTH = 300;
- public static final int DEFAULT_HEIGHT = 200;
- }
結果顯示: