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;
}
結果顯示:
CTRL+Y:
CTRL+B:
CTRL+R: