所有 Swing 組件都是 JavaBeans 組件。它們有一系列的 setter 和 getter 方法,這些方法的類似於 void setXXX(類型名) 和 Type getXXX() 。關於這些方法沒有什麼特別之處,並且正如所預期的,它們遵循 JavaBeans 的屬性命名規范。我們今天要討論的是JavaBeans 組件的一個方面,即一對監聽器方法 addXXXListener (XXXListener name) 和 removeXXXListener (XXXListener name) 。 XXListener 在這裡指的是一個監聽器對象,它擴展了 EventListener 接口,等候與監聽器關聯的組件中的各種事件發生。當事件發生時,所有注冊的監聽器都會得到事件的通知(沒有特定的順序)。通過魔術般的一個小反射(reflection)和一個新的 java.beans.EventHandler 類,您可以將一個監聽器附加到一個 bean 上,而無需直接實現這個監聽器接口或者創建那些煩人的小匿名內部類。
以前的方法
在深入到使用新的 EventHandler 類的細節之前,讓我們回顧一下不使用這個類時是如何進行工作的。我們舉一個對 Swing 框架中的按鈕選擇做出響應的簡單例子。選擇一個按鈕生成一個 ActionEvent 。要對這個事件做出響應,需要將 ActionListener 附加到這個按鈕上,如清單 1 所示:
清單 1. 監聽標准按鈕選擇
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonSelection extends JFrame {
public ButtonSelection() {
super("Selection");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JButton button = new JButton("Pick Me");
Container contentPane = getContentPane();
contentPane.add(button, BorderLayout.CENTER);
button.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Hello, World!");
}
}
);
}
public static void main(String args[]) {
JFrame frame = new ButtonSelection();
frame.setSize(200, 100);
frame.show();
}
}
這裡沒有任何神奇之處,您可能已經熟悉這種代碼了。這裡, ActionListener 實現是適時定義的,它定義為一個匿名內部類,並直接附加到按鈕上。在選擇這個按鈕時,字符串 Hello, World! 就打印到控制台中。與程序關聯的屏幕如圖 1 所示:
圖 1. 帶 ActionListener 的按鈕選擇
在 JavaBeans 規范中,沒有要求您創建匿名內部類進行事件監聽。 IDE 工具常常采用這種行為:您說要一個監聽器,它就生成一個 stub,然後您填入細節。完成同樣工作的其他方式包括在調用類中提供指定的實現或者實現您自己的接口。
定義了每一個實現類後,就會創建一個單獨的.class 文件。所以,在前面的 ButtonSelection 程序中,您會看到編譯器生成兩個 .class 文件:ButtonSelection.class 和 ButtonSelection$1.class。 $1 是 Sun 編譯器命名匿名內部類的方式,計數隨著每一個類增加。其他編譯器可能有不同的命名方式。
用 EventHandler 注冊監聽器
EventHandler 類提供了另一種將監聽器注冊到 JavaBeans 組件上的方法。它不是創建一個實現了接口的類、並將這個實現注冊到需要監聽的事件所在的組件上,而是創建一個 EventHandler 實例並注冊它。雖然這個類有一個公共構造函數,但一般不使用它而是使用三個靜態 create() 方法,如清單 2 所示:
清單 2. EventHandler 的 create() 方法
public static Object create(Class listenerInterface,
Object target,
String action)
public static Object create(Class listenerInterface,
Object target,
String action,
String eventPropertyName)
public static Object create(Class listenerInterface,
Object target,
String action,
String eventPropertyName,
String listenerMethodName)
讓我們更詳細地看一下這三種方法。
使用 create(Class, Object, String)
因為第一種方法的參數最少,所以它最簡單。第一個參數是 EventListener 類型,您要實現的就是它的接口,例如,要響應按鈕選擇,這個參數應該是 ActionListener.class ,以表示這個接口的 Class 對象。雖然 ActionListener 只有接口中的一個方法,但是以這種方式創建接口的一個實現意味著這個接口實現的所有方法都將執行同樣的代碼。
第二和第三個參數是相互關聯的。它們結合在一起說明調用 Object 目標的 String 操作方法。然後使用反射,您有一個 ActionListener 實現,但是沒有在文件系統中增加一個 .class 文件。清單 3 重復了前面 圖 1 中的按鈕選擇例子,使用了一個 EventHandler 。注意 println() 調用需要轉移到一個方法中,這樣就可以從處理程序中調用它。
清單 3. 展示 create(Class, Object, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class ButtonEventHandler extends JFrame {
public ButtonEventHandler() {
super("Selection");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JButton button = new JButton("Pick Me");
Container contentPane = getContentPane();
contentPane.add(button, BorderLayout.CENTER);
button.addActionListener(
(ActionListener)EventHandler.create(
ActionListener.class,
this,
"print")
);
}
public void print() {
System.out.println("Hello, World!");
}
public static void main(String args[]) {
JFrame frame = new ButtonEventHandler();
frame.setSize(200, 100);
frame.show();
}
}
create() 中調用 EventHandler 的代碼只是表示“在需要通知按鈕所附的 ActionListener 時,調用我們的 print() 方法( this )”。不過有一些副作用。第一個是調用需要強制類型轉換,以返回正確的監聽器類型,從而滿足編譯器要求。另一個副作用是由於對 print() 的調用是通過反射間接進行的,所以這個方法必須是公共的(並且不接受參數)。使用 EventHandler 的另一個特點是對於其他版本的 create() 來說,很少出現問題。
使用 create(Class, Object, String, String)
下一版本的 create() 添加了第四個參數,並增加了第三個參數的用途。第一個 String 參數現在也可以表示 Object 參數的可寫 JavaBeans 屬性的名字。所以,對於 JButton ,如果第三個參數是 text ,那麼這相當於一個 setText() 調用,該方法所需要的參數是由傳遞給第四個參數的 String 來表示的。
第四個參數使您可以訪問事件的可讀屬性,用第三個參數傳遞的值設置可寫屬性。為了示范這一點,清單 4 提供了一個用於輸入的 JTextField 組件和一個用於文本顯示的 JLabel 組件。在 JTextField 中的按 Return 鍵時,生成一個 ActionEvent ,並且標簽的文字變為 JTextField 中的內容。
清單 4. 展示 create(Class, Object, String, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class TextFieldHandler extends JFrame {
public TextFieldHandler() {
super("Selection");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JTextField text = new JTextField();
JLabel label = new JLabel();
Container contentPane = getContentPane();
contentPane.add(text, BorderLayout.NORTH);
contentPane.add(label, BorderLayout.CENTER);
text.addActionListener(
(ActionListener)EventHandler.create(
ActionListener.class,
label,
"text",
"source.text")
);
}
public static void main(String args[]) {
JFrame frame = new TextFieldHandler();
frame.setSize(200, 150);
frame.show();
}
}
圖 2 顯示了這個程序的外觀。在文本字段中輸入文字並按 Return 鍵。這會使 ActionListener 產生 EventHandler.create(ActionListener.class, label, "text", "source.text") 調用,其中 source.text 表明要得到事件源的 text 屬性,直接映射到 label.setText((JTextField(event.getSource())).getText()) 代碼。
圖 2. 處理文本字段輸入
使用 create(Class, Object, String, String, String)
最後一種版本的 create() 是將另外兩種方法結合在一起使用,對於在其他調用中沒有的參數,則傳遞 null 。其他版本的 create() 要求您對所有監聽器接口方法做同樣的事情,這最後一種方法讓您可以指定對每一種監聽器方法調用不同的操作。所以,對於一個 MouseListener ,您可以為 mousePressed() 調用一種操作,為 mouseReleased() 調用另一種操作、還可以為 mouseClicked() 調用其他的操作。清單 5 展示了最後一種版本的 create() ,它只有用於鼠標按下/釋放事件的兩種簡單的打印方法:
清單 5. 展示 create(Class, Object, String, String, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class MouseHandler extends JFrame {
public MouseHandler() {
super("Press and Release Mouse");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container contentPane = getContentPane();
contentPane.addMouseListener(
(MouseListener)EventHandler.create(
MouseListener.class,
this,
"pressed",
"point",
"mousePressed")
);
contentPane.addMouseListener(
(MouseListener)EventHandler.create(
MouseListener.class,
this,
"released",
"point",
"mouseReleased")
);
}
public void pressed(Point p) {
System.out.println("Pressed at: " + p);
}
public void released(Point p) {
System.out.println("Released at: " + p);
}
public static void main(String args[]) {
JFrame frame = new MouseHandler();
frame.setSize(400, 400);
frame.show();
}
}
這個程序沒有什麼不尋常的地方,只有一個大的空屏幕,可以在其中按下和釋放鼠標。不過要注意屏幕附加了兩個鼠標監聽器,而不是一個。對每個監聽器,其他的方法實質上都是沉寂的(stubbed out)。還要注意 pressed() 和 released() 方法有一個參數是事件的 Point 。對於不接受參數的方法,在指定 point 的地方需要一個 null 。
結束語
這就是有關使用 EventHandler 全部內容。是否要使用它呢?我個人認為這是一種風格問題。在內部它用到了反射,所以可能會稍微慢一些。它還要求調用方法為公共的。如果 IDE 替我生成了代碼,那麼我可能就讓它保持原樣,而不會將編碼監聽器重新編碼為匿名內部類。