1. 程式人生 > 實用技巧 >事件處理

事件處理

第十一章 事 件 處 理

事件處理基礎

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);
      }
   }
}


滑鼠事件

當用戶點選滑鼠按鈕時, 將會呼叫三個監聽器方法: 鼠 標第一次被按下時呼叫 mousePressed ; 滑鼠被釋放時呼叫 mouseReleased; 最後呼叫 mouseClicked。如果只對最終的點 擊事件感興趣, 就可以忽略前兩個方法。用 MouseEvent 類 物件作為引數, 呼叫 getX 和 getY 方法可以獲得滑鼠被按下 時滑鼠指標所在的 x 和 y座標。要想區分單擊、 雙擊和三擊(! ) ,需要使用 getClickCount 方法。

還可以利用 Toolkit 類中的 createCustomCursor 方法自定義游標型別: Toolkit tk = Toolkit.getDefaultToolkit(): Image img = tk.getlmage("dynamite.gif"): Cursor dynamiteCursor = tk.createCustomCijrsor(inig, new Point(10, 10), "dynamite stick"); createCustomCursor 的第一個引數指向游標影象。第二個引數給出了游標的“ 熱點” 偏移。 第三個引數是一個描述游標的字串。這個字串可以用於訪問性支援, 例如, 可以將游標形式讀給視力受損或沒有在螢幕前面的人。

如 果 用 戶 在 移 動 鼠 標 的 同 時 按 下 鼠 標, 就 會 調 用 mouseMoved 而 不 是 調 用 mouseDmgged。在測試應用程式中, 使用者可以用游標拖動小方塊。在程式中,僅僅用拖動的 矩形更新當前游標位置。然後,重新繪製畫布, 以顯示新的滑鼠位置。

public void mouseDragged(MouseEvent event) { 
    if (current != null) { 
        int x = event.getX(); 
        int y = event.getY(); 
        current.setFrame(x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENCTH, SIDELENCTH);
        repaint();
    }
}

[注] 只有滑鼠在一個元件內部停留才會呼叫 mouseMoved 方法。然而, 即使滑鼠拖動 到元件外面, mouseDragged 方法也會被呼叫。

在示例程式中, 對兩種滑鼠事件型別都感興趣。這裡定義了兩個內部類:MouseHandler 和 MouseMotionHandler。 MouseHandler 類擴充套件於 MouseAdapter 類, 這是因為它只定義了 5 個 MouseListener 方法中的兩個方法。MouseMotionHandler 實現了 MouseMotionListener 介面, 並定義了這個介面中的兩個方法。程式清單 11-4 是這個程式的清單。

//程式清單 11-4 mouse/MouseFrame.java
package mouse;
​
import javax.swing.*;
​
/**
 * A frame containing a panel for testing mouse operations
 */
public class MouseFrame extends JFrame
{
   public MouseFrame()
   {
      add(new MouseComponent());
      pack();
   }
}

//程式清單 11-5 mouse/MouseComponent.java 
package mouse;
​
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
​
/**
 * A component with mouse operations for adding and removing squares.
 */
public class MouseComponent extends JComponent
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 200;
​
   private static final int SIDELENGTH = 10;
   private ArrayList<Rectangle2D> squares;
   private Rectangle2D current; // the square containing the mouse cursor
public MouseComponent()
   {
      squares = new ArrayList<>();
      current = null;
​
      addMouseListener(new MouseHandler());
      addMouseMotionListener(new MouseMotionHandler());
   }
​
   public void paintComponent(Graphics g)
   {
      Graphics2D g2 = (Graphics2D) g;
​
      // draw all squares
      for (Rectangle2D r : squares)
         g2.draw(r);
   }
​
   /**
    * Finds the first square containing a point.
    * @param p a point
    * @return the first square that contains p
    */
   public Rectangle2D find(Point2D p)
   {
      for (Rectangle2D r : squares)
      {
         if (r.contains(p)) return r;
      }
      return null;
   }
​
   /**
    * Adds a square to the collection.
    * @param p the center of the square
    */
   public void add(Point2D p)
   {
      double x = p.getX();
      double y = p.getY();
​
      current = new Rectangle2D.Double(x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH,
            SIDELENGTH);
      squares.add(current);
      repaint();
   }
​
   /**
    * Removes a square from the collection.
    * @param s the square to remove
    */
   public void remove(Rectangle2D s)
   {
      if (s == null) return;
      if (s == current) current = null;
      squares.remove(s);
      repaint();
   }
​
   private class MouseHandler extends MouseAdapter
   {
      public void mousePressed(MouseEvent event)
      {
         // add a new square if the cursor isn't inside a square
         current = find(event.getPoint());
         if (current == null) add(event.getPoint());
      }
​
      public void mouseClicked(MouseEvent event)
      {
         // remove the current square if double clicked
         current = find(event.getPoint());
         if (current != null && event.getClickCount() >= 2) remove(current);
      }
   }
​
   private class MouseMotionHandler implements MouseMotionListener
   {
      public void mouseMoved(MouseEvent event)
      {
         // set the mouse cursor to cross hairs if it is inside
         // a rectangle
if (find(event.getPoint()) == null) setCursor(Cursor.getDefaultCursor());
         else setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
      }
​
      public void mouseDragged(MouseEvent event)
      {
         if (current != null)
         {
            int x = event.getX();
            int y = event.getY();
​
            // drag the current rectangle to center it at (x, y)
            current.setFrame(x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH);
            repaint();
         }
      }
   }
   
   public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}


AWT 事件繼承層次

EventObject 類有一個子類 AWTEvent,它是所有 AWT 事件類的父類。圖 11-8 顯示了 AWT 事件的繼承關係圖。

有些 Swing元件將生成其他事件型別的事件物件;它們都直接擴充套件於 EventObject, 而不 是 AWTEvent。

語義事件和底層事件

AWT 將事件分為底層(low-level) 事件和語義(semantic) 事件。語義事件是表示使用者動 作的事件, 例如,點選按鈕;因此,ActionEvent 是一種語義事件。底層事件是形成那些事件 的事件。在點選按鈕時,包含了按下滑鼠、連續移動滑鼠、 抬起滑鼠(只有滑鼠在按鈕區中 抬起才引發)事件。或者在使用者利用 TAB 鍵選擇按鈕,並利用空格鍵啟用它時,發生的敲擊 鍵盤事件。同樣,調節滾動條是一種語義事件,但拖動滑鼠是底層事件。

下面是 java.awt.event 包中最常用的語義事件類:

•ActionEvent (對應按鈕點選、 選單選擇、選擇列表項或在文字框中 ENTER);

•AdjustmentEvent (使用者調節滾動條);

•ItemEvem (使用者從複選框或列表框中選擇一項) 。

常用的 5 個底層事件類是:

•KeyEvent (一個鍵被按下或釋放);

•MouseEvent (滑鼠鍵被按下、 釋放、 移動或拖動);

•MouseWheelEvent (滑鼠滾輪被轉動);

•FocusEvent (某個元件獲得焦點或失去焦點);

•WindowEvent ( 視窗狀態被改變) 。

下列介面將監聽這些事件。

ActionListener 
AdjustmentListener
FocusListener
ItemListener
KeyListener
MouseListener
MouseMotionListener
MouseWheelListener
WindowListener
WindowFocusListener
WindowStateListener

有幾個 AWT 監聽器介面包含多個方法, 它們都配有一個介面卡類, 在這個類中實現了 相應介面中的所有方法,但每個方法沒有做任何事情 (有些介面只包含一個方法, 因此, 就 沒有必要為它們定義介面卡類丫)。下面是常用的介面卡類:

 FocusAdapter MouseMotionAdapter KeyAdapter WindowAdapter MouseAdapter 

表 11-4 顯示了最重要的 AWT 監聽器介面、事件和事件源。