事件處理
事件處理基礎
AWT 事件處理機制的概要:
•監聽器物件是一個實現了特定監聽器介面(listener interface) 的類的例項。
•事件源是一個能夠註冊監聽器物件併發送事件物件的物件。
•當事件發生時,事件源將事件物件傳遞給所有註冊的監聽器。
•監聽器物件將利用事件物件中的資訊決定如何對事件做出響應。
圖 11-1顯示了事件處理類和介面之間的關係。
下面是監聽器的一個示例:
ActionListener listener = . . .; JButton button = new JButton("0K"); button.addActionListenerGistener);
現在, 只要按鈕產生了一個“ 動作事件”,listener 物件就會得到通告。對於按鈕來說, 正像我們所想到的,動作事件就是點選按鈕。
為了實現 ActionListener 介面,監聽器類必須有一個被稱為 actionPerformed 的方法,該 方法接收一個 ActionEvent 物件引數。
class MyListener iipleaents ActionListener { ... public void actionPerforied(ActionEvent event) { // reaction to button click goes here... } }
只要使用者點選按鈕,JButton 物件就會建立一個 ActionEvent 物件, 然後呼叫 listener, action Performed (event) 傳遞事件物件。可以將多個監聽器物件新增到一個像按鈕這樣的事件源中。這樣一來, 只要使用者點選按鈕,按鈕就會呼叫所有監聽器的 actionPerformed方法。
例項:處理按鈕點選事件
程式清單li-i包含了完整的框架類。無論何時點選任何一個按鈕,對應的動作監聽器就 會修改面板的背景顏色。
//程式清單 button/ButtonFrame.java package button; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * A frame with a button panel */ public class ButtonFrame extends JFrame { private JPanel buttonPanel; private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 200; public ButtonFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // create buttons JButton yellowButton = new JButton("Yellow"); JButton blueButton = new JButton("Blue"); JButton redButton = new JButton("Red"); buttonPanel = new JPanel(); // add buttons to panel buttonPanel.add(yellowButton); buttonPanel.add(blueButton); buttonPanel.add(redButton); // add panel to frame add(buttonPanel); // create button actions ColorAction yellowAction = new ColorAction(Color.YELLOW); ColorAction blueAction = new ColorAction(Color.BLUE); ColorAction redAction = new ColorAction(Color.RED); // associate actions with buttons yellowButton.addActionListener(yellowAction); blueButton.addActionListener(blueAction); redButton.addActionListener(redAction); } /** * An action listener that sets the panel's background color. */ private class ColorAction implements ActionListener { private Color backgroundColor; public ColorAction(Color c) { backgroundColor = c; } public void actionPerformed(ActionEvent event) { buttonPanel.setBackground(backgroundColor); } } }
簡潔地指定監聽器
如上一節中的彩色按鈕。在這種情況 下, 可以實現一個輔助方法:
public void makeButton(String name, Color backgroundedor) { JButton button = new JButton(name); buttonPanel.add(button); button.addActionListener(event -> buttonPanel.setBackground(backgroundColor)); }
需要說明, lambda 表示式指示引數變數backgroundColor。 然後只需要呼叫:
makeButton("yellow", Color.YELLOW); makeButton("blue", Color.BLUE); makeButton("red", Color.RED);
在這裡, 我們構造了 3 個監聽器物件, 分別對應一種顏色, 但並沒有顯式定義一個 類。每次呼叫這個輔助方法時,它會建立實現了 ActionListener 介面的一個類的例項 它的 actionPerformed動作會引用實際上隨監聽器物件儲存的backGroundColor 值。不過, 所有這 些會自動完成,而無需顯式定義監聽器類、例項變數或設定這些變數的構造器。
例項:改變觀感
在預設情況下,Swing 程式使用 Metal 觀感,可以採用兩種方式改變觀感。第一種方式 是在 Java 安裝的子目錄jre/lib下有一個檔案 swing.properties。在這個檔案中,將屬性 swing, defaultlaf設定為所希望的觀感類名。例如,
swing,defaultlaf=com.sun.java.swlng.plaf.motif.MotifLookAndFeel
注意, Metal 和 Nimbus 觀感位於javax.swing 包中。其他的觀感包位於 com.sun.java 包 中, 並且不是在每個 Java 實現中都提供。現在, 鑑於版權的原因, Windows 和 Macintosh 的 觀感包只與 Windows 和 Macintosh 版本的 Java執行時環境一起釋出。
由於屬性檔案中以 # 字元開始的行被忽略, 所以, 可以在 swing.properties 檔案中 提供幾種觀感選擇, 並通過增刪 # 字元來切換選擇:
#swing.defaultlaf=javax.swing.piaf.metal.MetalLookAndFeel swing.default af=com.sun.java.swing.plaf.motif.MotifLookAndFeel #swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel
採用這種方式開啟觀感時必須重新啟動程式。Swing 程式只在啟動時讀取一次 swing. properties 檔案。
第二種方式是動態地改變觀感。這需要呼叫靜態的 UIManager.setLookAndFeel 方法,並 提供所想要的觀感類名, 然後再呼叫靜態方法 SwingUtilities.updateComponentTreeUI 來重新整理 全部的元件集。這裡需要向這個方法提供一個元件,並由此找到其他的所有元件。
下面是一個示例,它顯示瞭如何用程式切換至 Motif 觀感:
String className = "com.sun.java.swing.piaf.motif.MotifLookAndFeel": try { UIManager.setLookAndFeel(className); SwingUtilities.updateComponentTreeUI(frame); pack(); } catch(Exception e) { e.printStackTrace(); }
程式清單 11-2 是一個完整的程式,它演示瞭如何切換觀感的方式。這 個程式與程式清單 11-1 十分相似。我們遵循前一 節的建議, 使用輔助方法 makeButton 和匿名內部 類指定按鈕動作, 即切換觀感。
//程式清單 11-2 plaf/PlafFrame.java package plaf; import java.awt.event.*; import javax.swing.*; /** * A frame with a button panel for changing look and feel */ public class PlafFrame extends JFrame { private JPanel buttonPanel; public PlafFrame() { buttonPanel = new JPanel(); UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels(); for (UIManager.LookAndFeelInfo info : infos) makeButton(info.getName(), info.getClassName()); add(buttonPanel); pack(); } /** * Makes a button to change the pluggable look and feel. * @param name the button name * @param plafName the name of the look and feel class */ void makeButton(String name, final String plafName) { // add button to panel JButton button = new JButton(name); buttonPanel.add(button); // set button action button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { // button action: switch to the new look and feel try { UIManager.setLookAndFeel(plafName); SwingUtilities.updateComponentTreeUI(PlafFrame.this); pack(); } catch (Exception e) { e.printStackTrace(); } } }); } }
介面卡類
當程式使用者試圖關閉一個框架視窗時,JFrame 物件就是 WindowEvent 的事件源。 如果 希望捕獲這個事件, 就必須有一個合適的監聽器物件, 並將它新增到框架的視窗監聽器列 表中。
WindowListener listener = . . . ;
frame.addWindowListener(listener);
視窗監聽器必須是實現 WindowListener介面的類的一個物件。在 WindowListener 介面 中包含 7 個方法。當發生視窗事件時,框架將呼叫這些方法響應 7 個不同的事件。從它們 的名字就可以得知其作用, 唯一的例外是在 Windows 下,通常將 iconified (圖示化)稱為 minimized (最小化)。下面是完整的 WindowListener介面:
public interface WindowListener { void windowOpened(WindowEvent e); void windowClosing(WindowEvent e): void windowClosed(WindowEvent e); void windowlconified(WindowEvent e); void windowDeiconified(WindowEvent e); void windowActivated(WindowEvent e); void windowDeactivated(WindowEvent e); }
鑑於簡化的目的,每個含有 多個方法的 AWT 監聽器介面都配有一個介面卡(adapter) 類,這個類實現了介面中的所有方 法, 但每個方法沒有做任何事情。這意味著介面卡類自動地滿足了 Java 實現相關監聽器介面 的技術需求。可以通過擴充套件介面卡類來指定對某些事件的響應動作,而不必實現介面中的每 個方法。
下面使用視窗介面卡。首先定義一個 WindowAdapter 類的擴充套件類, 其中包含繼承的 6 個沒有 做任何事情的方法和一個覆蓋的方法 windowClosing:
class Terminator extends WindowAdapter { public void windowCIosing(WindowEvent e) { if {useragrees) System.exit(0); } }
現在, 可以將一個 Terminator 物件註冊為事件 監聽器:
WindowListener listener = new Terminator(); frame.addWindowListener(listener);
只要框架產生了視窗事件, 就會通過呼叫 7 個方法之中的一個方法將事件傳遞給 listener 物件 (如圖 11-5 所示),其中 6 個方法沒有做任何事情; windowClosing方法呼叫 System.exit(0)終止應用程 序的執行。
動 作
無論改變背景顏色的命令是通過哪種方式下達的,是點選按鈕、選單選擇,還是 按鍵 ’ 其處理都是一樣的。
Swing包提供了一種非常實用的機制來封裝命令,並將它們連線到多個事件源,這就是 Action 介面。一個動作是一個封裝下列內容的物件:
•命令的說明(一個文字字串和一個可選圖示);
•執行命令所需要的引數(例如,在列舉的例子中請求改變的顏色)。
Action 介面包含下列方法:
void actionPerformed(ActionEvent event) void setEnabled(boolean b) boolean isEnabled() void putValue(String key, Object value) Object getValue(String key) void addPropertyChangeListener(PropertyChangeListener listener) void removePropertyChangeListener(PropertyChangeListener listener)
第一個方法是 ActionListener介面中很熟悉的一個: 實際上,Action介面擴充套件於 Action Listener 介面,因此,可以在任何需要 ActionListener物件的地方使用 Action物件。
putValue 和 getvalue方法允許儲存和檢索動作物件中的任意名 / 值。有兩個重要的預定 義字串:Action.NAME 和 Action.SMALL_ICON,用於將動作的名字和圖示儲存到一個動作物件中:
action.putValue(Action._E, "Blue"); action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball.gif"));
表 11-1 給出了所有預定義的動作表名稱。
Action 介面的最後兩個方法能夠讓其他物件在動作物件的屬性發生變化時得到通告,尤 其是選單或工具欄觸發的動作。例如, 如果增加一個選單, 作為動作物件的屬性變更監聽 器, 而這個動作物件隨後被禁用,選單就會被呼叫,並將動作名稱變為灰色。屬性變更監聽 器是一種常用的構造形式, 它是 JavaBeans元件模型的一部分。
下面總結一下用同一個動作響應按鈕、選單項或按鍵的方式:
1 ) 實現一個擴充套件於 AbstractAction類的類。多個相關的動作可以使用同一個類。
2 ) 構造一個動作類的物件。
3 ) 使用動作物件建立按鈕或選單項。構造器將從動作物件中讀取標籤文字和圖示。
4 ) 為了能夠通過按鍵觸發動作, 必須額外地執行幾步操作。首先定位頂層視窗元件, 例如,包含所有其他元件的面板。
5 ) 然後, 得到頂層元件的 WHEN_ANCESTOR_OF_FOCUS_COMPONENT 輸入對映。 為需要的按鍵建立一個 KeyStrike 物件。建立一個描述動作字串這樣的動作鍵物件。將(按 鍵, 動作鍵)對新增到輸人對映中。
6 ) 最後,得到頂層元件的動作對映。將(動作鍵,動作物件)新增到對映中。
程式清單 11-3 給出了將按鈕和按鍵對映到動作物件的完整程式程式碼。 試試看, 點選按鈕 或按下 CTRL+Y、 CTRL+B 或 CTRL+R 來改變面板顏色。
//程式清單 11-3 action/ActionFrame.java package action; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * A frame with a panel that demonstrates color change actions. */ public class ActionFrame extends JFrame { private JPanel buttonPanel; private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 200; public ActionFrame() { 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 { /** * Constructs a color action. * @param name the name to show on the button * @param icon the icon to display on the button * @param c the background color */ 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); } } }