Java Action 動作

  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給出了所有預定義的動作表名稱。

表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對象都與動作關聯。三個輸入映射對應着三個不同的條件。

表2 輸入映射條件
標誌激活條件
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:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章