# 13.16 新型AWT
在Java 1.1中一個顯著的改變就是完善了新AWT的創新。大多數的改變圍繞在Java 1.1中使用的新事件模型:老的事件模型是糟糕的、笨拙的、非面向對象的,而新的事件模型可能是我所見過的最優秀的。難以理解一個如此糟糕的(老的AWT)和一個如此優秀的(新的事件模型)程序語言居然出自同一個集團之手。新的考慮事件的方法看來中止了,因此爭議不再變成障礙,從而輕易進入我們的意識里;相反,它是一個幫助我們設計系統的工具。它同樣是Java Beans的精華,我們會在本章后面部分進入講述。
新的方法設計對象做為“事件源”和“事件接收器”以代替老AWT的非面向對象串聯的條件語句。正象我們將看到的內部類的用途是集成面向對象的原始狀態的新事件。另外,事件現在被描繪為在一個類體系以取代單一的類并且我們可以創建自己的事件類型。
我們同樣會發現,如果我們采用老的AWT編程,Java 1.1版會產生一些看起來不合理的名字轉換。例如,`setsize()`改成`resize()`。當我們學習Java Beans時這會變得更加的合理,因為Beans使用一個獨特的命名協議。名字必須被修改以在Beans中產生新的標準AWT組件。
剪貼板操作在Java 1.1版中也得到支持,盡管拖放操作“將在新版本中被支持”。我們可能訪問桌面色彩組織,所以我們的Java可以同其余桌面保持一致。可以利用彈出式菜單,并且為圖像和圖形作了改進。也同樣支持鼠標操作。還有簡單的為打印的API以及簡單地支持滾動。
## 13.16.1 新的事件模型
在新的事件模型的組件可以開始一個事件。每種類型的事件被一個個別的類所描繪。當事件開始后,它受理一個或更多事件指明“接收器”。因此,事件源和處理事件的地址可以被分離。
每個事件接收器都是執行特定的接收器類型接口的類對象。因此作為一個程序開發者,我們所要做的是創建接收器對象并且在被激活事件的組件中進行注冊。`event-firing`組件調用一個`addXXXListener()`方法來完成注冊,以描述`XXX`事件類型接受。我們可以容易地了解到以`addListened`名的方法通知我們任何的事件類型都可以被處理,如果我們試圖接收事件我們會發現編譯時我們的錯誤。Java Beans同樣使用這種`addListener`名的方法去判斷那一個程序可以運行。
我們所有的事件邏輯將裝入到一個接收器類中。當我們創建一個接收器類時唯一的一點限制是必須執行專用的接口。我們可以創建一個全局接收器類,這種情況在內部類中有助于被很好地使用,不僅僅是因為它們提供了一個理論上的接收器類組到它們服務的UI或業務邏輯類中,但因為(正像我們將會在本章后面看到的)事實是一個內部類維持一個引用到它的父對象,提供了一個很好的通過類和子系統邊界的調用方法。
一個簡單的例子將使這一切變得清晰明確。同時思考本章前部`Button2.java`例子與這個例子的差異。
```
//: Button2New.java
// Capturing button presses
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;
public class Button2New extends Applet {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public void init() {
b1.addActionListener(new B1());
b2.addActionListener(new B2());
add(b1);
add(b2);
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
getAppletContext().showStatus("Button 1");
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
getAppletContext().showStatus("Button 2");
}
}
/* The old way:
public boolean action(Event evt, Object arg) {
if(evt.target.equals(b1))
getAppletContext().showStatus("Button 1");
else if(evt.target.equals(b2))
getAppletContext().showStatus("Button 2");
// Let the base class handle it:
else
return super.action(evt, arg);
return true; // We've handled it here
}
*/
} ///:~
```
我們可比較兩種方法,老的代碼在左面作為注解。在`init()`方法里,只有一個改變就是增加了下面的兩行:
```
b1.addActionListener(new B1());
b2.addActionListener(new B2());
```
按鈕按下時,`addActionListener()`通知按鈕對象被激活。`B1`和`B2`類都是執行接口`ActionListener`的內部類。這個接口包括一個單一的方法`actionPerformed()`(這意味著當事件激活時,這個動作將被執行)。注意`actionPreformed()`方法不是一個普通事件,說得更恰當些是一個特殊類型的事件,`ActionEvent`。如果我們想提取特殊`ActionEvent`的信息,因此我們不需要故意去測試和向下轉換參數。
對編程者來說一個最好的事便是`actionPerformed()`十分的簡單易用。它是一個可以調用的方法。同老的`action()`方法比較,老的方法我們必須指出發生了什么和適當的動作,同樣,我們會擔心調用基類`action()`的版本并且返回一個值去指明是否被處理。在新的事件模型中,我們知道所有事件測試推理自動進行,因此我們不必指出發生了什么;我們剛剛表示發生了什么,它就自動地完成了。如果我們還沒有提出用新的方法覆蓋老的方法,我們會很快提出。
## 13.16.2 事件和接收者類型
所有AWT組件都被改變成包含`addXXXListener()`和`removeXXXListener()`方法,因此特定的接收器類型可從每個組件中增加和刪除。我們會注意到`XXX`在每個場合中同樣表示參數的方法,例如,`addFooListener(FooListener fl)`。下面這張表格總結了通過提供`addXXXListener()`和`removeXXXListener()`方法,從而支持那些特定事件的相關事件、接收器、方法以及組件。
+ 事件,接收器接口及添加和刪除方法
+ 支持這個事件的組件
+ `ActionEvent`
+ `ActionListener`
+ `addActionListener( )`
+ `removeActionListener( )`
+ `Button`, `List`, `TextField`, `MenuItem`, and its derivatives including `CheckboxMenuItem`, `Menu`, and `PopupMenu`
+ `AdjustmentEvent`
+ `AdjustmentListener`
+ `addAdjustmentListener( )`
+ `removeAdjustmentListener( )`
+ `Scrollbar`
+ Anything you create that implements the `Adjustable` interface
+ `ComponentEvent`
+ `ComponentListener`
+ `addComponentListener( )`
+ `removeComponentListener( )`
+ `Component` and its derivatives, including `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea`, and `TextField`
+ `ContainerEvent`
+ `ContainerListener`
+ `addContainerListener( )`
+ `removeContainerListener( )`
+ `Container` and its derivatives, including `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, and `Frame`
+ `FocusEvent`
+ `FocusListener`
+ `addFocusListener( )`
+ `removeFocusListener( )`
+ `Component` and its derivatives, including `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame ``Label`, `List`, `Scrollbar`, `TextArea`, `and ``TextField`
+ `KeyEvent`
+ `KeyListener`
+ `addKeyListener( )`
+ `removeKeyListener( )`
+ `Component` and its derivatives, including `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea`, and `TextField`
+ `MouseEvent `(`for ``both ``clicks ``and ``motion`)
+ `MouseListener`
+ `addMouseListener( )`
+ `removeMouseListener( )`
+ `Component` and its derivatives, including `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea`, and `TextField`
+ `MouseEvent`[`55`] (`for ``both ``clicks ``and ``motion`)
+ `MouseMotionListener`
+ `addMouseMotionListener( )`
+ `removeMouseMotionListener( )`
+ `Component` and its derivatives, including `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea`, and `TextField`
+ `WindowEvent`
+ `WindowListener`
+ `addWindowListener( )`
+ `removeWindowListener( )`
+ `Window` and its derivatives, including `Dialog`, `FileDialog`, and `Frame`
+ `ItemEvent`
+ `ItemListener`
+ `addItemListener( )`
+ `removeItemListener( )`
+ `Checkbox`, `CheckboxMenuItem`, `Choice`, `List`, and anything that implements the `ItemSelectable` interface
+ `TextEvent`
+ `TextListener`
+ `addTextListener( )`
+ `removeTextListener( )`
+ Anything derived from `TextComponent`, including `TextArea` and `TextField`
⑤:即使表面上如此,但實際上并沒有`MouseMotiionEvent`(鼠標運動事件)。單擊和運動都組合到`MouseEvent`里,所以`MouseEvent`在表格中的這種另類行為并非一個錯誤。
可以看到,每種類型的組件只為特定類型的事件提供了支持。這有助于我們發現由每種組件支持的事件,如下表所示:
+ 組件類型
+ 支持的事件
+ `Adjustable`
+ `AdjustmentEvent`
+ `Applet`
+ `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Button`
+ `ActionEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Canvas`
+ `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Checkbox`
+ `ItemEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `CheckboxMenuItem`
+ `ActionEvent`, `ItemEvent`
+ `Choice`
+ `ItemEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Component`
+ `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Container`
+ `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Dialog`
+ `ContainerEvent`, `WindowEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `FileDialog`
+ `ContainerEvent`, `WindowEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Frame`
+ `ContainerEvent`, `WindowEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Label`
+ `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `List`
+ `ActionEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ItemEvent`, `ComponentEvent`
+ `Menu`
+ `ActionEvent`
+ `MenuItem`
+ `ActionEvent`
+ `Panel`
+ `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `PopupMenu`
+ `ActionEvent`
+ `Scrollbar`
+ `AdjustmentEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `ScrollPane`
+ `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `TextArea`
+ `TextEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `TextComponent`
+ `TextEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `TextField`
+ `ActionEvent`, `TextEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
+ `Window`
+ `ContainerEvent`, `WindowEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent`
一旦知道了一個特定的組件支持哪些事件,就不必再去尋找任何東西來響應那個事件。只需簡單地:
(1) 取得事件類的名字,并刪掉其中的`Event`字樣。在剩下的部分加入`Listener`字樣。這就是在我們的內部類里需要實現的接收器接口。
(2) 實現上面的接口,針對想要捕獲的事件編寫方法代碼。例如,假設我們想捕獲鼠標的移動,所以需要為`MouseMotiionListener`接口的`mouseMoved()`方法編寫代(當然還必須實現其他一些方法,但這里有捷徑可循,馬上就會講到這個問題)。
(3) 為步驟2中的接收器類創建一個對象。隨自己的組件和方法完成對它的注冊,方法是在接收器的名字里加入一個前綴`add`。比如`addMouseMotionListener()`。
下表是對接收器接口的一個總結:
+ 接收器接口
+ 接口中的方法
+ `ActionListener`
+ `actionPerformed(ActionEvent)`
+ `AdjustmentListener`
+ `adjustmentValueChanged(AdjustmentEvent)`
+ `ComponentListener`
+ `ComponentAdapter`
+ `componentHidden(ComponentEvent)`
+ `componentShown(ComponentEvent)`
+ `componentMoved(ComponentEvent)`
+ `componentResized(ComponentEvent)`
+ `ContainerListener`
+ `ContainerAdapter`
+ `componentAdded(ContainerEvent)`
+ `componentRemoved(ContainerEvent)`
+ `FocusListener`
+ `FocusAdapter`
+ `focusGained(FocusEvent)`
+ `focusLost(FocusEvent)`
+ `KeyListener`
+ `KeyAdapter`
+ `keyPressed(KeyEvent)`
+ `keyReleased(KeyEvent)`
+ `keyTyped(KeyEvent)`
+ `MouseListener`
+ `MouseAdapter`
+ `mouseClicked(MouseEvent)`
+ `mouseEntered(MouseEvent)`
+ `mouseExited(MouseEvent)`
+ `mousePressed(MouseEvent)`
+ `mouseReleased(MouseEvent)`
+ `MouseMotionListener`
+ `MouseMotionAdapter`
+ `mouseDragged(MouseEvent)`
+ `mouseMoved(MouseEvent)`
+ `WindowListener`
+ `WindowAdapter`
+ `windowOpened(WindowEvent)`
+ `windowClosing(WindowEvent)`
+ `windowClosed(WindowEvent)`
+ `windowActivated(WindowEvent)`
+ `windowDeactivated(WindowEvent)`
+ `windowIconified(WindowEvent)`
+ `windowDeiconified(WindowEvent)`
+ `ItemListener`
+ `itemStateChanged(ItemEvent)`
+ `TextListener`
+ `textValueChanged(TextEvent)`
(1) 用接收器適配器簡化操作
在上面的表格中,我們可以注意到一些接收器接口只有唯一的一個方法。它們的執行是無輕重的,因為我們僅當需要書寫特殊方法時才會執行它們。然而,接收器接口擁有多個方法,使用起來卻不太友好。例如,我們必須一直運行某些事物,當我們創建一個應用程序時對幀提供一個`WindowListener`,以便當我們得到`windowClosing()`事件時可以調用`System.exit(0)`以退出應用程序。但因為`WindowListener`是一個接口,我們必須執行其它所有的方法即使它們不運行任何事件。這真令人討厭。
為了解決這個問題,每個擁有超過一個方法的接收器接口都可擁有適配器,它們的名我們可以在上面的表格中看到。每個適配器為每個接口方法提供默認的方法。(`WindowAdapter`的默認方法不是`windowClosing()`,而是`System.exit(0)`方法。)此外我們所要做的就是從適配器處繼承并重載唯一的需要變更的方法。例如,典型的`WindowListener`我們會像下面這樣的使用。
```
class MyWindowListener extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
```
適配器的全部宗旨就是使接收器的創建變得更加簡便。
但所謂的“適配器”也有一個缺點,而且較難發覺。假定我們象上面那樣寫一個`WindowAdapter`:
```
class MyWindowListener extends WindowAdapter {
public void WindowClosing(WindowEvent e) {
System.exit(0);
}
}
```
表面上一切正常,但實際沒有任何效果。每個事件的編譯和運行都很正常——只是關閉窗口不會退出程序。您注意到問題在哪里嗎?在方法的名字里:是`WindowClosing()`,而不是`windowClosing()`。大小寫的一個簡單失誤就會造成一個嶄新的方法。但是,這并非我們關閉窗口時調用的方法,所以當然沒有任何效果。
## 13.16.3 用Java 1.1 AWT制作窗口和程序片
我們經常都需要創建一個類,使其既可作為一個窗口調用,亦可作為一個程序片調用。為做到這一點,只需為程序片簡單地加入一個m`ain()`即可,令其在一個`Frame`(幀)里構建程序片的一個實例。作為一個簡單的示例,下面讓我們來看看如何對`Button2New.java`作一番修改,使其能同時作為應用程序和程序片使用:
```
//: Button2NewB.java
// An application and an applet
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;
public class Button2NewB extends Applet {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
TextField t = new TextField(20);
public void init() {
b1.addActionListener(new B1());
b2.addActionListener(new B2());
add(b1);
add(b2);
add(t);
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Button 1");
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Button 2");
}
}
// To close the application:
static class WL extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
// A main() for the application:
public static void main(String[] args) {
Button2NewB applet = new Button2NewB();
Frame aFrame = new Frame("Button2NewB");
aFrame.addWindowListener(new WL());
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
內部類`WL`和`main()`方法是加入程序片的唯一兩個元素,程序片剩余的部分則原封未動。事實上,我們通常將`WL`類和`main()`方法做一結小的改進復制和粘貼到我們自己的程序片里(請記住創建內部類時通常需要一個外部類來處理它,形成它靜態地消除這個需要)。我們可以看到在`main()`方法里,程序片明確地初始化和開始,因為在這個例子里瀏覽器不能為我們有效地運行它。當然,這不會提供全部的瀏覽器調用`stop()`和`destroy()`的行為,但對大多數的情況而言它都是可接受的。如果它變成一個麻煩,我們可以:
(1) 使程序片引用為一個靜態類(以代替局部可變的`main()`),然后:
(2) 在我們調用`System.exit()`之前在`WindowAdapter.windowClosing()`中調用`applet.stop()`和`applet.destroy()`。
注意最后一行:
```
aFrame.setVisible(true);
```
這是Java 1.1 AWT的一個改變。`show()`方法不再被支持,而`setVisible(true)`則取代了`show()`方法。當我們在本章后面部分學習Java Beans時,這些表面上易于改變的方法將會變得更加的合理。
這個例子同樣被使用`TextField`修改而不是顯示到控制臺或瀏覽器狀態行上。在開發程序時有一個限制條件就是程序片和應用程序我們都必須根據它們的運行情況選擇輸入和輸出結構。
這里展示了Java 1.1 AWT的其它小的新功能。我們不再需要去使用有錯誤傾向的利用字符串指定`BorderLayout`定位的方法。當我們增加一個元素到Java 1.1版的`BorderLayout`中時,我們可以這樣寫:
```
aFrame.add(applet, BorderLayout.CENTER);
```
我們對位置規定一個`BorderLayout`的常數,以使它能在編譯時被檢驗(而不是對老的結構悄悄地做不合適的事)。這是一個顯著的改善,并且將在這本書的余下部分大量地使用。
(2) 將窗口接收器變成匿名類
任何一個接收器類都可作為一個匿名類執行,但這一直有個意外,那就是我們可能需要在其它場合使用它們的功能。但是,窗口接收器在這里僅作為關閉應用程序窗口來使用,因此我們可以安全地制造一個匿名類。然后,`main()`中的下面這行代碼:
```
aFrame.addWindowListener(new WL());
```
會變成:
```
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
```
這有一個優點就是它不需要其它的類名。我們必須對自己判斷是否它使代碼變得易于理解或者更難。不過,對本書余下部分而言,匿名內部類將通常被使用在窗口接收器中。
(3) 將程序片封裝到JAR文件里
一個重要的JAR應用就是完善程序片的裝載。在Java 1.0版中,人們傾向于試法將它們的代碼填入到單個的程序片類里,因此客戶只需要單個的服務器就可適合下載程序片代碼。但這不僅使結果凌亂,難以閱讀(當然維護也然)程序,但類文件一直不能壓縮,因此下載從來沒有快過。
JAR文件將我們所有的被壓縮的類文件打包到一個單個兒的文件中,再被瀏覽器下載。現在我們不需要創建一個糟糕的設計以最小化我們創建的類,并且用戶將得到更快地下載速度。
仔細想想上面的例子,這個例子看起來像`Button2NewB`,是一個單類,但事實上它包含三個內部類,因此共有四個。每當我們編譯程序,我會用這行代碼打包它到一個JAR文件:
```
jar cf Button2NewB.jar *.class
```
這是假定只有一個類文件在當前目錄中,其中之一來自`Button2NewB.java`(否則我們會得到特別的打包)。
現在我們可以創建一個使用新文件標簽來指定JAR文件的HTML頁,如下所示:
```
<head><title>Button2NewB Example Applet
</title></head>
<body>
<applet code="Button2NewB.class"
archive="Button2NewB.jar"
width=200 height=150>
</applet>
</body>
```
與HTML文件中的程序片標記有關的其他任何內容都保持不變。
## 13.16.4 再研究一下以前的例子
為注意到一些利用新事件模型的例子和為學習程序從老到新事件模型改變的方法,下面的例子回到在本章第一部分利用事件模型來證明的一些爭議。另外,每個程序包括程序片和應用程序現在都可以借助或不借助瀏覽器來運行。
(1) 文本字段
這個例子同`TextField1.java`相似,但它增加了顯然額外的行為:
```
//: TextNew.java
// Text fields with Java 1.1 events
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class TextNew extends Applet {
Button
b1 = new Button("Get Text"),
b2 = new Button("Set Text");
TextField
t1 = new TextField(30),
t2 = new TextField(30),
t3 = new TextField(30);
String s = new String();
public void init() {
b1.addActionListener(new B1());
b2.addActionListener(new B2());
t1.addTextListener(new T1());
t1.addActionListener(new T1A());
t1.addKeyListener(new T1K());
add(b1);
add(b2);
add(t1);
add(t2);
add(t3);
}
class T1 implements TextListener {
public void textValueChanged(TextEvent e) {
t2.setText(t1.getText());
}
}
class T1A implements ActionListener {
private int count = 0;
public void actionPerformed(ActionEvent e) {
t3.setText("t1 Action Event " + count++);
}
}
class T1K extends KeyAdapter {
public void keyTyped(KeyEvent e) {
String ts = t1.getText();
if(e.getKeyChar() ==
KeyEvent.VK_BACK_SPACE) {
// Ensure it's not empty:
if( ts.length() > 0) {
ts = ts.substring(0, ts.length() - 1);
t1.setText(ts);
}
}
else
t1.setText(
t1.getText() +
Character.toUpperCase(
e.getKeyChar()));
t1.setCaretPosition(
t1.getText().length());
// Stop regular character from appearing:
e.consume();
}
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
s = t1.getSelectedText();
if(s.length() == 0) s = t1.getText();
t1.setEditable(true);
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
t1.setText("Inserted by Button 2: " + s);
t1.setEditable(false);
}
}
public static void main(String[] args) {
TextNew applet = new TextNew();
Frame aFrame = new Frame("TextNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
當`TextField t1`的動作接收器被激活時,`TextField t3`就是一個需要報告的場所。我們注意到僅當我們按下`enter`鍵時,動作接收器才會為`TextField`所激活。
`TextField t1`附有幾個接收器。`T1`接收器從`t1`復制所有文字到`t2`,強制所有字符串轉換成大寫。我們會發現這兩個工作同是進行的,并且如果我們增加`T1K`接收器后我們再增加`T1`接收器,它就不那么重要:在文字字段內的所有的字符串將一直被強制變為大寫。這看起來鍵盤事件一直在文字組件事件前被激活,并且如果我們需要保留`t2`的字符串原來輸入時的樣子,我們就必須做一些特別的工作。
`T1K`有著其它的一些有趣的活動。我們必須測試`backspace`(因為我們現在控制著每一個事件)并執行刪除。`caret`必須被明確地設置到字段的結尾;否則它不會像我們希望的運行。最后,為了防止原來的字符串被默認的機制所處理,事件必須利用為事件對象而存在的`consume()`方法所“耗盡”。這會通知系統停止激活其余特殊事件的事件處理器。
這個例子同樣無聲地證明了設計內部類的帶來的諸多優點。注意下面的內部類:
```
class T1 implements TextListener {
public void textValueChanged(TextEvent e) {
t2.setText(t1.getText());
}
}
```
`t1`和`t2`不屬于`T1`的一部分,并且到目前為止它們都是很容易理解的,沒有任何的特殊限制。這是因為一個內部類的對象能自動地捕捉一個引用到外部的創建它的對象那里,因此我們可以處理封裝類對象的方法和內容。正像我們看到的,這十分方便(注釋⑥)。
⑥:它也解決了“回調”的問題,不必為Java加入任何令人惱火的“方法指針”特性。
(2) 文本區域
Java 1.1版中`Text Area`最重要的改變就滾動條。對于`TextArea`的構造器而言,我們可以立即控制`TextArea`是否會擁有滾動條:水平的,垂直的,兩者都有或者都沒有。這個例子更正了前面Java 1.0版`TextArea1.java`程序片,演示了Java 1.1版的滾動條構造器:
```
//: TextAreaNew.java
// Controlling scrollbars with the TextArea
// component in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class TextAreaNew extends Applet {
Button b1 = new Button("Text Area 1");
Button b2 = new Button("Text Area 2");
Button b3 = new Button("Replace Text");
Button b4 = new Button("Insert Text");
TextArea t1 = new TextArea("t1", 1, 30);
TextArea t2 = new TextArea("t2", 4, 30);
TextArea t3 = new TextArea("t3", 1, 30,
TextArea.SCROLLBARS_NONE);
TextArea t4 = new TextArea("t4", 10, 10,
TextArea.SCROLLBARS_VERTICAL_ONLY);
TextArea t5 = new TextArea("t5", 4, 30,
TextArea.SCROLLBARS_HORIZONTAL_ONLY);
TextArea t6 = new TextArea("t6", 10, 10,
TextArea.SCROLLBARS_BOTH);
public void init() {
b1.addActionListener(new B1L());
add(b1);
add(t1);
b2.addActionListener(new B2L());
add(b2);
add(t2);
b3.addActionListener(new B3L());
add(b3);
b4.addActionListener(new B4L());
add(b4);
add(t3); add(t4); add(t5); add(t6);
}
class B1L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t5.append(t1.getText() + "\n");
}
}
class B2L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t2.setText("Inserted by Button 2");
t2.append(": " + t1.getText());
t5.append(t2.getText() + "\n");
}
}
class B3L implements ActionListener {
public void actionPerformed(ActionEvent e) {
String s = " Replacement ";
t2.replaceRange(s, 3, 3 + s.length());
}
}
class B4L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t2.insert(" Inserted ", 10);
}
}
public static void main(String[] args) {
TextAreaNew applet = new TextAreaNew();
Frame aFrame = new Frame("TextAreaNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,725);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
我們發現只能在構造`TextArea`時能夠控制滾動條。同樣,即使`TE AR`沒有滾動條,我們滾動光標也將被制止(可通過運行這個例子中驗證這種行為)。
(3) 復選框和單選鈕
正如早先指出的那樣,復選框和單選鈕都是同一個類建立的。單選鈕和復選框略有不同,它是復選框安置到`CheckboxGroup`中構成的。在其中任一種情況下,有趣的`ItemEvent`事件為我們創建一個`ItemListener`項目接收器。
當處理一組復選框或者單選鈕時,我們有一個不錯的選擇。我們可以創建一個新的內部類去為每個復選框處理事件,或者創建一個內部類判斷哪個復選框被單擊并注冊一個內部類單獨的對象為每個復選對象。下面的例子演示了兩種方法:
```
//: RadioCheckNew.java
// Radio buttons and Check Boxes in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class RadioCheckNew extends Applet {
TextField t = new TextField(30);
Checkbox[] cb = {
new Checkbox("Check Box 1"),
new Checkbox("Check Box 2"),
new Checkbox("Check Box 3") };
CheckboxGroup g = new CheckboxGroup();
Checkbox
cb4 = new Checkbox("four", g, false),
cb5 = new Checkbox("five", g, true),
cb6 = new Checkbox("six", g, false);
public void init() {
t.setEditable(false);
add(t);
ILCheck il = new ILCheck();
for(int i = 0; i < cb.length; i++) {
cb[i].addItemListener(il);
add(cb[i]);
}
cb4.addItemListener(new IL4());
cb5.addItemListener(new IL5());
cb6.addItemListener(new IL6());
add(cb4); add(cb5); add(cb6);
}
// Checking the source:
class ILCheck implements ItemListener {
public void itemStateChanged(ItemEvent e) {
for(int i = 0; i < cb.length; i++) {
if(e.getSource().equals(cb[i])) {
t.setText("Check box " + (i + 1));
return;
}
}
}
}
// vs. an individual class for each item:
class IL4 implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("Radio button four");
}
}
class IL5 implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("Radio button five");
}
}
class IL6 implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("Radio button six");
}
}
public static void main(String[] args) {
RadioCheckNew applet = new RadioCheckNew();
Frame aFrame = new Frame("RadioCheckNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
`ILCheck`擁有當我們增加或者減少復選框時自動調整的優點。當然,我們對單選鈕使用這種方法也同樣的好。但是,它僅當我們的邏輯足以普遍的支持這種方法時才會被使用。如果聲明一個確定的信號——我們將重復利用獨立的接收器類,否則我們將結束一串條件語句。
(4) 下拉列表
下拉列表在Java 1.1版中當一個選擇被改變時同樣使用`ItemListener`去告知我們:
```
//: ChoiceNew.java
// Drop-down lists with Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class ChoiceNew extends Applet {
String[] description = { "Ebullient", "Obtuse",
"Recalcitrant", "Brilliant", "Somnescent",
"Timorous", "Florid", "Putrescent" };
TextField t = new TextField(100);
Choice c = new Choice();
Button b = new Button("Add items");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
c.addItem(description[count++]);
add(t);
add(c);
add(b);
c.addItemListener(new CL());
b.addActionListener(new BL());
}
class CL implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("index: " + c.getSelectedIndex()
+ " " + e.toString());
}
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(count < description.length)
c.addItem(description[count++]);
}
}
public static void main(String[] args) {
ChoiceNew applet = new ChoiceNew();
Frame aFrame = new Frame("ChoiceNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(750,100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
這個程序中沒什么特別新穎的東西(除了Java 1.1版的UI類里少數幾個值得關注的缺陷)。
(5) 列表
我們消除了Java 1.0中`List`設計的一個缺陷,就是`List`不能像我們希望的那樣工作:它會與單擊在一個列表元素上發生沖突。
```
//: ListNew.java
// Java 1.1 Lists are easier to use
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class ListNew extends Applet {
String[] flavors = { "Chocolate", "Strawberry",
"Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie" };
// Show 6 items, allow multiple selection:
List lst = new List(6, true);
TextArea t = new TextArea(flavors.length, 30);
Button b = new Button("test");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
lst.addItem(flavors[count++]);
add(t);
add(lst);
add(b);
lst.addItemListener(new LL());
b.addActionListener(new BL());
}
class LL implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("");
String[] items = lst.getSelectedItems();
for(int i = 0; i < items.length; i++)
t.append(items[i] + "\n");
}
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(count < flavors.length)
lst.addItem(flavors[count++], 0);
}
}
public static void main(String[] args) {
ListNew applet = new ListNew();
Frame aFrame = new Frame("ListNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
我們可以注意到在列表項中無需特別的邏輯需要去支持一個單擊動作。我們正好像我們在其它地方所做的那樣附加上一個接收器。
(6) 菜單
為菜單處理事件看起來受益于Java 1.1版的事件模型,但Java生成菜單的方法常常麻煩并且需要一些手工編寫代碼。生成菜單的正確方法看起來像資源而不是一些代碼。請牢牢記住編程工具會廣泛地為我們處理創建的菜單,因此這可以減少我們的痛苦(只要它們會同樣處理維護任務!)。另外,我們將發現菜單不支持并且將導致混亂的事件:菜單項使用`ActionListeners`(動作接收器),但復選框菜單項使用`ItemListeners`(項目接收器)。菜單對象同樣能支持ActionListeners(動作接收器),但通常不那么有用。一般來說,我們會附加接收器到每個菜單項或復選框菜單項,但下面的例子(對先前例子的修改)演示了一個聯合捕捉多個菜單組件到一個單獨的接收器類的方法。正像我們將看到的,它或許不值得為這而激烈地爭論。
```
//: MenuNew.java
// Menus in Java 1.1
import java.awt.*;
import java.awt.event.*;
public class MenuNew extends Frame {
String[] flavors = { "Chocolate", "Strawberry",
"Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie" };
TextField t = new TextField("No flavor", 30);
MenuBar mb1 = new MenuBar();
Menu f = new Menu("File");
Menu m = new Menu("Flavors");
Menu s = new Menu("Safety");
// Alternative approach:
CheckboxMenuItem[] safety = {
new CheckboxMenuItem("Guard"),
new CheckboxMenuItem("Hide")
};
MenuItem[] file = {
// No menu shortcut:
new MenuItem("Open"),
// Adding a menu shortcut is very simple:
new MenuItem("Exit",
new MenuShortcut(KeyEvent.VK_E))
};
// A second menu bar to swap to:
MenuBar mb2 = new MenuBar();
Menu fooBar = new Menu("fooBar");
MenuItem[] other = {
new MenuItem("Foo"),
new MenuItem("Bar"),
new MenuItem("Baz"),
};
// Initialization code:
{
ML ml = new ML();
CMIL cmil = new CMIL();
safety[0].setActionCommand("Guard");
safety[0].addItemListener(cmil);
safety[1].setActionCommand("Hide");
safety[1].addItemListener(cmil);
file[0].setActionCommand("Open");
file[0].addActionListener(ml);
file[1].setActionCommand("Exit");
file[1].addActionListener(ml);
other[0].addActionListener(new FooL());
other[1].addActionListener(new BarL());
other[2].addActionListener(new BazL());
}
Button b = new Button("Swap Menus");
public MenuNew() {
FL fl = new FL();
for(int i = 0; i < flavors.length; i++) {
MenuItem mi = new MenuItem(flavors[i]);
mi.addActionListener(fl);
m.add(mi);
// Add separators at intervals:
if((i+1) % 3 == 0)
m.addSeparator();
}
for(int i = 0; i < safety.length; i++)
s.add(safety[i]);
f.add(s);
for(int i = 0; i < file.length; i++)
f.add(file[i]);
mb1.add(f);
mb1.add(m);
setMenuBar(mb1);
t.setEditable(false);
add(t, BorderLayout.CENTER);
// Set up the system for swapping menus:
b.addActionListener(new BL());
add(b, BorderLayout.NORTH);
for(int i = 0; i < other.length; i++)
fooBar.add(other[i]);
mb2.add(fooBar);
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuBar m = getMenuBar();
if(m == mb1) setMenuBar(mb2);
else if (m == mb2) setMenuBar(mb1);
}
}
class ML implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuItem target = (MenuItem)e.getSource();
String actionCommand =
target.getActionCommand();
if(actionCommand.equals("Open")) {
String s = t.getText();
boolean chosen = false;
for(int i = 0; i < flavors.length; i++)
if(s.equals(flavors[i])) chosen = true;
if(!chosen)
t.setText("Choose a flavor first!");
else
t.setText("Opening "+ s +". Mmm, mm!");
} else if(actionCommand.equals("Exit")) {
dispatchEvent(
new WindowEvent(MenuNew.this,
WindowEvent.WINDOW_CLOSING));
}
}
}
class FL implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuItem target = (MenuItem)e.getSource();
t.setText(target.getLabel());
}
}
// Alternatively, you can create a different
// class for each different MenuItem. Then you
// Don't have to figure out which one it is:
class FooL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Foo selected");
}
}
class BarL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Bar selected");
}
}
class BazL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Baz selected");
}
}
class CMIL implements ItemListener {
public void itemStateChanged(ItemEvent e) {
CheckboxMenuItem target =
(CheckboxMenuItem)e.getSource();
String actionCommand =
target.getActionCommand();
if(actionCommand.equals("Guard"))
t.setText("Guard the Ice Cream! " +
"Guarding is " + target.getState());
else if(actionCommand.equals("Hide"))
t.setText("Hide the Ice Cream! " +
"Is it cold? " + target.getState());
}
}
public static void main(String[] args) {
MenuNew f = new MenuNew();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setSize(300,200);
f.setVisible(true);
}
} ///:~
```
在我們開始初始化節(由注解`Initialization code:`后的右大括號指明)的前面部分的代碼同先前(Java 1.0版)版本相同。這里我們可以注意到項目接收器和動作接收器被附加在不同的菜單組件上。
Java 1.1支持“菜單快捷鍵”,因此我們可以選擇一個菜單項目利用鍵盤替代鼠標。這十分的簡單;我們只要使用重載菜單項構造器設置第二個參數為一個`MenuShortcut`(菜單快捷鍵事件)對象即可。菜單快捷鍵構造器設置重要的方法,當它按下時不可思議地顯示在菜單項上。上面的例子增加了`Control-E`到`Exit`菜單項中。
我們同樣會注意`setActionCommand()`的使用。這看似一點陌生因為在各種情況下“action command”完全同菜單組件上的標簽一樣。為什么不正好使用標簽代替可選擇的字符串呢?這個難題是國際化的。如果我們重新用其它語言寫這個程序,我們只需要改變菜單中的標簽,并不審查代碼中可能包含新錯誤的所有邏輯。因此使這對檢查文字字符串聯合菜單組件的代碼而言變得簡單容易,當菜單標簽能改變時“動作指令”可以不作任何的改變。所有這些代碼同“動作指令”一同工作,因此它不會受改變菜單標簽的影響。注意在這個程序中,不是所有的菜單組件都被它們的動作指令所審查,因此這些組件都沒有它們的動作指令集。
大多數的構造器同前面的一樣,將幾個調用的異常增加到接收器中。大量的工作發生在接收器里。在前面例子的`BL`中,菜單交替發生。在`ML`中,“尋找`ring`”方法被作為動作事件(`ActionEvent`)的資源并對它進行轉換送入菜單項,然后得到動作指令字符串,再通過它去貫穿串聯組,當然條件是對它進行聲明。這些大多數同前面的一樣,但請注意如果`Exit`被選中,通過進入封裝類對象的引用(`MenuNew.this`)并創建一個`WINDOW_CLOSING`事件,一個新的窗口事件就被創建了。新的事件被分配到封裝類對象的`dispatchEvent()`方法,然后結束調用`windowsClosing()`內部幀的窗口接收器(這個接收器作為一個內部類被創建在`main()`里),似乎這是“正常”產生消息的方法。通過這種機制,我們可以在任何情況下迅速處理任何的信息,因此,它非常的強大。
FL接收器是很簡單盡管它能處理特殊菜單的所有不同的特色。如果我們的邏輯十分的簡單明了,這種方法對我們就很有用處,但通常,我們使用這種方法時需要與`FooL`,`BarL`和`BazL`一道使用,它們每個都附加到一個單獨的菜單組件上,因此必然無需測試邏輯,并且使我們正確地辨識出誰調用了接收器。這種方法產生了大量的類,內部代碼趨向于變得小巧和處理起來簡單、安全。
(7) 對話框
在這個例子里直接重寫了早期的`ToeTest.java`程序。在這個新的版本里,任何事件都被安放進一個內部類中。雖然這完全消除了需要記錄產生的任何類的麻煩,作為`ToeTest.java`的一個例子,它能使內部類的概念變得不那遙遠。在這點,內嵌類被嵌套達四層之深!我們需要的這種設計決定了內部類的優點是否值得增加更加復雜的事物。另外,當我們創建一個非靜態的內部類時,我們將捆綁非靜態類到它周圍的類上。有時,單獨的類可以更容易地被復用。
```
//: ToeTestNew.java
// Demonstration of dialog boxes
// and creating your own components
import java.awt.*;
import java.awt.event.*;
public class ToeTestNew extends Frame {
TextField rows = new TextField("3");
TextField cols = new TextField("3");
public ToeTestNew() {
setTitle("Toe Test");
Panel p = new Panel();
p.setLayout(new GridLayout(2,2));
p.add(new Label("Rows", Label.CENTER));
p.add(rows);
p.add(new Label("Columns", Label.CENTER));
p.add(cols);
add(p, BorderLayout.NORTH);
Button b = new Button("go");
b.addActionListener(new BL());
add(b, BorderLayout.SOUTH);
}
static final int BLANK = 0;
static final int XX = 1;
static final int OO = 2;
class ToeDialog extends Dialog {
// w = number of cells wide
// h = number of cells high
int turn = XX; // Start with x's turn
public ToeDialog(int w, int h) {
super(ToeTestNew.this,
"The game itself", false);
setLayout(new GridLayout(w, h));
for(int i = 0; i < w * h; i++)
add(new ToeButton());
setSize(w * 50, h * 50);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e){
dispose();
}
});
}
class ToeButton extends Canvas {
int state = BLANK;
ToeButton() {
addMouseListener(new ML());
}
public void paint(Graphics g) {
int x1 = 0;
int y1 = 0;
int x2 = getSize().width - 1;
int y2 = getSize().height - 1;
g.drawRect(x1, y1, x2, y2);
x1 = x2/4;
y1 = y2/4;
int wide = x2/2;
int high = y2/2;
if(state == XX) {
g.drawLine(x1, y1,
x1 + wide, y1 + high);
g.drawLine(x1, y1 + high,
x1 + wide, y1);
}
if(state == OO) {
g.drawOval(x1, y1,
x1 + wide/2, y1 + high/2);
}
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
if(state == BLANK) {
state = turn;
turn = (turn == XX ? OO : XX);
}
else
state = (state == XX ? OO : XX);
repaint();
}
}
}
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
Dialog d = new ToeDialog(
Integer.parseInt(rows.getText()),
Integer.parseInt(cols.getText()));
d.show();
}
}
public static void main(String[] args) {
Frame f = new ToeTestNew();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setSize(200,100);
f.setVisible(true);
}
} ///:~
```
由于“靜態”的東西只能位于類的外部一級,所以內部類不可能擁有靜態數據或者靜態內部類。
(8) 文件對話框
這個例子是直接用新事件模型對`FileDialogTest.java`修改而來。
```
//: FileDialogNew.java
// Demonstration of File dialog boxes
import java.awt.*;
import java.awt.event.*;
public class FileDialogNew extends Frame {
TextField filename = new TextField();
TextField directory = new TextField();
Button open = new Button("Open");
Button save = new Button("Save");
public FileDialogNew() {
setTitle("File Dialog Test");
Panel p = new Panel();
p.setLayout(new FlowLayout());
open.addActionListener(new OpenL());
p.add(open);
save.addActionListener(new SaveL());
p.add(save);
add(p, BorderLayout.SOUTH);
directory.setEditable(false);
filename.setEditable(false);
p = new Panel();
p.setLayout(new GridLayout(2,1));
p.add(filename);
p.add(directory);
add(p, BorderLayout.NORTH);
}
class OpenL implements ActionListener {
public void actionPerformed(ActionEvent e) {
// Two arguments, defaults to open file:
FileDialog d = new FileDialog(
FileDialogNew.this,
"What file do you want to open?");
d.setFile("*.java");
d.setDirectory("."); // Current directory
d.show();
String yourFile = "*.*";
if((yourFile = d.getFile()) != null) {
filename.setText(yourFile);
directory.setText(d.getDirectory());
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
}
}
class SaveL implements ActionListener {
public void actionPerformed(ActionEvent e) {
FileDialog d = new FileDialog(
FileDialogNew.this,
"What file do you want to save?",
FileDialog.SAVE);
d.setFile("*.java");
d.setDirectory(".");
d.show();
String saveFile;
if((saveFile = d.getFile()) != null) {
filename.setText(saveFile);
directory.setText(d.getDirectory());
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
}
}
public static void main(String[] args) {
Frame f = new FileDialogNew();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setSize(250,110);
f.setVisible(true);
}
} ///:~
```
如果所有的改變是這樣的容易那將有多棒,但至少它們已足夠容易,并且我們的代碼已受益于這改進的可讀性上。
## 13.16.5 動態綁定事件
新AWT事件模型給我們帶來的一個好處就是靈活性。在老的模型中我們被迫為我們的程序動作艱難地編寫代碼。但新的模型我們可以用單一方法調用增加和刪除事件動作。下面的例子證明了這一點:
```
//: DynamicEvents.java
// The new Java 1.1 event model allows you to
// change event behavior dynamically. Also
// demonstrates multiple actions for an event.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class DynamicEvents extends Frame {
Vector v = new Vector();
int i = 0;
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public DynamicEvents() {
setLayout(new FlowLayout());
b1.addActionListener(new B());
b1.addActionListener(new B1());
b2.addActionListener(new B());
b2.addActionListener(new B2());
add(b1);
add(b2);
}
class B implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("A button was pressed");
}
}
class CountListener implements ActionListener {
int index;
public CountListener(int i) { index = i; }
public void actionPerformed(ActionEvent e) {
System.out.println(
"Counted Listener " + index);
}
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Button 1 pressed");
ActionListener a = new CountListener(i++);
v.addElement(a);
b2.addActionListener(a);
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Button 2 pressed");
int end = v.size() -1;
if(end >= 0) {
b2.removeActionListener(
(ActionListener)v.elementAt(end));
v.removeElementAt(end);
}
}
}
public static void main(String[] args) {
Frame f = new DynamicEvents();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
f.setSize(300,200);
f.show();
}
} ///:~
```
這個例子采取的新手法包括:
(1) 在每個按鈕上附著不少于一個的接收器。通常,組件把事件作為多轉換處理,這意味著我們可以為單個事件注冊許多接收器。當在特殊的組件中一個事件作為單一轉換被處理時,我們會得到`TooManyListenersException`(即太多接收器異常)。
(2) 程序執行期間,接收器動態地被從按鈕B2中增加和刪除。增加用我們前面見到過的方法完成,但每個組件同樣有一個`removeXXXListener()`(刪除`XXX`接收器)方法來刪除各種類型的接收器。
這種靈活性為我們的編程提供了更強大的能力。
我們注意到事件接收器不能保證在命令他們被增加時可被調用(雖然事實上大部分的執行工作都是用這種方法完成的)。
## 13.16.6 將事務邏輯與UI邏輯區分開
一般而言,我們需要設計我們的類如此以至于每一類做“一件事”。當涉及用戶接口代碼時就更顯得尤為重要,因為它很容易地封裝“您要做什么”和“怎樣顯示它”。這種有效的配合防止了代碼的重復使用。更不用說它令人滿意的從GUI中區分出我們的“事物邏輯”。使用這種方法,我們可以不僅僅更容易地重復使用事物邏輯,它同樣可以更容易地重復使用GUI。
其它的爭議是“動作對象”存在的完成分離機器的多層次系統。動作主要的定位規則允許所有新事件修改后立刻生效,并且這是如此一個引人注目的設置系統的方法。但是這些動作對象可以被在一些不同的應用程序使用并且因此不會被一些特殊的顯示模式所約束。它們會合理地執行動作操作并且沒有多余的事件。
下面的例子演示了從GUI代碼中多么地輕松的區分事物邏輯:
```
//: Separation.java
// Separating GUI logic and business objects
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class BusinessLogic {
private int modifier;
BusinessLogic(int mod) {
modifier = mod;
}
public void setModifier(int mod) {
modifier = mod;
}
public int getModifier() {
return modifier;
}
// Some business operations:
public int calculation1(int arg) {
return arg * modifier;
}
public int calculation2(int arg) {
return arg + modifier;
}
}
public class Separation extends Applet {
TextField
t = new TextField(20),
mod = new TextField(20);
BusinessLogic bl = new BusinessLogic(2);
Button
calc1 = new Button("Calculation 1"),
calc2 = new Button("Calculation 2");
public void init() {
add(t);
calc1.addActionListener(new Calc1L());
calc2.addActionListener(new Calc2L());
add(calc1); add(calc2);
mod.addTextListener(new ModL());
add(new Label("Modifier:"));
add(mod);
}
static int getValue(TextField tf) {
try {
return Integer.parseInt(tf.getText());
} catch(NumberFormatException e) {
return 0;
}
}
class Calc1L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText(Integer.toString(
bl.calculation1(getValue(t))));
}
}
class Calc2L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText(Integer.toString(
bl.calculation2(getValue(t))));
}
}
class ModL implements TextListener {
public void textValueChanged(TextEvent e) {
bl.setModifier(getValue(mod));
}
}
public static void main(String[] args) {
Separation applet = new Separation();
Frame aFrame = new Frame("Separation");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(200,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
```
可以看到,事物邏輯是一個直接完成它的操作而不需要提示并且可以在GUI環境下使用的類。它正適合它的工作。區分動作記錄了所有UI的詳細資料,并且它只通過它的公共接口與事物邏輯交流。所有的操作圍繞中心通過UI和事物邏輯對象來回獲取信息。因此區分,輪流做它的工作。因為區分中只知道它同事物邏輯對象對話(也就是說,它沒有高度的結合),它可以被強迫同其它類型的對象對話而沒有更多的煩惱。
思考從事物邏輯中區分UI的條件,同樣思考當我們調整傳統的Java代碼使它運行時,怎樣使它更易存活。
## 13.16.7 推薦編碼方法
內部類是新的事件模型,并且事實上舊的事件模型連同新庫的特征都被它好的支持,依賴老式的編程方法無疑增加了一個新的混亂的因素。現在有更多不同的方法為我們編寫討厭的代碼。湊巧的是,這種代碼顯現在本書中和程序樣本中,并且甚至在文件和程序樣本中同SUN公司區別開來。在這一節中,我們將看到一些關于我們會和不會運行新AWT的爭執,并由向我們展示除了可以原諒的情況,我們可以隨時使用接收器類去解決我們的事件處理需要來結束。因為這種方法同樣是最簡單和最清晰的方法,它將會對我們學習它構成有效的幫助。
在看到任何事以前,我們知道盡管Java 1.1向后兼容Java 1.0(也就是說,我們可以在1.1中編譯和運行1.0的程序),但我們并不能在同一個程序里混合事件模型。換言之,當我們試圖集成老的代碼到一個新的程序中時,我們不能使用老式的`action()`方法在同一個程序中,因此我們必須決定是否對新程序使用老的,難以維護的方法或者升級老的代碼。這不會有太多的競爭因為新的方法對老的方法而言是如此的優秀。
(1) 準則:運行它的好方法
為了給我們一些事物來進行比較,這兒有一個程序例子演示向我們推薦的方法。到現在它會變得相當的熟悉和舒適。
```
//: GoodIdea.java
// The best way to design classes using the new
// Java 1.1 event model: use an inner class for
// each different event. This maximizes
// flexibility and modularity.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class GoodIdea extends Frame {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public GoodIdea() {
setLayout(new FlowLayout());
b1.addActionListener(new B1L());
b2.addActionListener(new B2L());
add(b1);
add(b2);
}
public class B1L implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Button 1 pressed");
}
}
public class B2L implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Button 2 pressed");
}
}
public static void main(String[] args) {
Frame f = new GoodIdea();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.out.println("Window Closing");
System.exit(0);
}
});
f.setSize(300,200);
f.setVisible(true);
}
} ///:~
```
這是頗有點微不足道的:每個按鈕有它自己的印出一些事物到控制臺的接收器。但請注意在整個程序中這不是一個條件語句,或者是一些表示“我想要知道怎樣使事件發生”的語句。每塊代碼都與運行有關,而不是類型檢驗。也就是說,這是最好的編寫我們的代碼的方法;不僅僅是它更易使我們理解概念,至少是使我們更易閱讀和維護。剪切和粘貼到新的程序是同樣如此的容易。
(2) 將主類作為接收器實現
第一個壞主意是一個通常的和推薦的方法。這使得主類(有代表性的是程序片或幀,但它能變成一些類)執行各種不同的接收器。下面是一個例子:
```
//: BadIdea1.java
// Some literature recommends this approach,
// but it's missing the point of the new event
// model in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class BadIdea1 extends Frame
implements ActionListener, WindowListener {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public BadIdea1() {
setLayout(new FlowLayout());
addWindowListener(this);
b1.addActionListener(this);
b2.addActionListener(this);
add(b1);
add(b2);
}
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if(source == b1)
System.out.println("Button 1 pressed");
else if(source == b2)
System.out.println("Button 2 pressed");
else
System.out.println("Something else");
}
public void windowClosing(WindowEvent e) {
System.out.println("Window Closing");
System.exit(0);
}
public void windowClosed(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public static void main(String[] args) {
Frame f = new BadIdea1();
f.setSize(300,200);
f.setVisible(true);
}
} ///:~
```
這樣做的用途顯示在下述三行里:
```
addWindowListener(this);
b1.addActionListener(this);
b2.addActionListener(this);
```
因為`Badidea1`執行動作接收器和窗中接收器,這些程序行當然可以接受,并且如果我們一直堅持設法使少量的類去減少服務器檢索期間的程序片載入的作法,它看起來變成一個不錯的主意。但是:
(1) Java 1.1版支持JAR文件,因此所有我們的文件可以被放置到一個單一的壓縮的JAR文件中,只需要一次服務器檢索。我們不再需要為Internet效率而減少類的數量。
(2) 上面的代碼的組件更加的少,因此它難以抓住和粘貼。注意我們必須不僅要執行各種各樣的接口為我們的主類,但在`actionPerformed()`方法中,我們利用一串條件語句測試哪個動作被完成了。不僅僅是這個狀態倒退,遠離接收器模型,除此之外,我們不能簡單地重復使用`actionPerformed()`方法因為它是指定為這個特殊的應用程序使用的。將這個程序例子與`GoodIdea.java`進行比較,我們可以正好捕捉一個接收器類并粘貼它和最小的焦急到任何地方。另外我們可以為一個單獨的事件注冊多個接收器類,允許甚至更多的模塊在每個接收器類在每個接收器中運行。
(3) 方法的混合
第二個bad idea混合了兩種方法:使用內嵌接收器類,但同樣執行一個或更多的接收器接口以作為主類的一部分。這種方法無需在書中和文件中進行解釋,而且我可以臆測到Java開發者認為他們必須為不同的目的而采取不同的方法。但我們卻不必——在我們編程時,我們或許可能會傾向于使用內嵌接收器類。
```
//: BadIdea2.java
// An improvement over BadIdea1.java, since it
// uses the WindowAdapter as an inner class
// instead of implementing all the methods of
// WindowListener, but still misses the
// valuable modularity of inner classes
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class BadIdea2 extends Frame
implements ActionListener {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public BadIdea2() {
setLayout(new FlowLayout());
addWindowListener(new WL());
b1.addActionListener(this);
b2.addActionListener(this);
add(b1);
add(b2);
}
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if(source == b1)
System.out.println("Button 1 pressed");
else if(source == b2)
System.out.println("Button 2 pressed");
else
System.out.println("Something else");
}
class WL extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.out.println("Window Closing");
System.exit(0);
}
}
public static void main(String[] args) {
Frame f = new BadIdea2();
f.setSize(300,200);
f.setVisible(true);
}
} ///:~
```
因為`actionPerformed()`動作完成方法同主類緊密地結合,所以難以復用代碼。它的代碼讀起來同樣是凌亂和令人厭煩的,遠遠超過了內部類方法。不合理的是,我們不得不在Java 1.1版中為事件使用那些老的思路。
(4) 繼承一個組件
創建一個新類型的組件時,在運行事件的老方法中,我們會經常看到不同的地方發生了變化。這里有一個程序例子來演示這種新的工作方法:
```
//: GoodTechnique.java
// Your first choice when overriding components
// should be to install listeners. The code is
// much safer, more modular and maintainable.
import java.awt.*;
import java.awt.event.*;
class Display {
public static final int
EVENT = 0, COMPONENT = 1,
MOUSE = 2, MOUSE_MOVE = 3,
FOCUS = 4, KEY = 5, ACTION = 6,
LAST = 7;
public String[] evnt;
Display() {
evnt = new String[LAST];
for(int i = 0; i < LAST; i++)
evnt[i] = new String();
}
public void show(Graphics g) {
for(int i = 0; i < LAST; i++)
g.drawString(evnt[i], 0, 10 * i + 10);
}
}
class EnabledPanel extends Panel {
Color c;
int id;
Display display = new Display();
public EnabledPanel(int i, Color mc) {
id = i;
c = mc;
setLayout(new BorderLayout());
add(new MyButton(), BorderLayout.SOUTH);
addComponentListener(new CL());
addFocusListener(new FL());
addKeyListener(new KL());
addMouseListener(new ML());
addMouseMotionListener(new MML());
}
// To eliminate flicker:
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
g.setColor(c);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
g.setColor(Color.black);
display.show(g);
}
// Don't need to enable anything for this:
public void processEvent(AWTEvent e) {
display.evnt[Display.EVENT]= e.toString();
repaint();
super.processEvent(e);
}
class CL implements ComponentListener {
public void componentMoved(ComponentEvent e){
display.evnt[Display.COMPONENT] =
"Component moved";
repaint();
}
public void
componentResized(ComponentEvent e) {
display.evnt[Display.COMPONENT] =
"Component resized";
repaint();
}
public void
componentHidden(ComponentEvent e) {
display.evnt[Display.COMPONENT] =
"Component hidden";
repaint();
}
public void componentShown(ComponentEvent e){
display.evnt[Display.COMPONENT] =
"Component shown";
repaint();
}
}
class FL implements FocusListener {
public void focusGained(FocusEvent e) {
display.evnt[Display.FOCUS] =
"FOCUS gained";
repaint();
}
public void focusLost(FocusEvent e) {
display.evnt[Display.FOCUS] =
"FOCUS lost";
repaint();
}
}
class KL implements KeyListener {
public void keyPressed(KeyEvent e) {
display.evnt[Display.KEY] =
"KEY pressed: ";
showCode(e);
}
public void keyReleased(KeyEvent e) {
display.evnt[Display.KEY] =
"KEY released: ";
showCode(e);
}
public void keyTyped(KeyEvent e) {
display.evnt[Display.KEY] =
"KEY typed: ";
showCode(e);
}
void showCode(KeyEvent e) {
int code = e.getKeyCode();
display.evnt[Display.KEY] +=
KeyEvent.getKeyText(code);
repaint();
}
}
class ML implements MouseListener {
public void mouseClicked(MouseEvent e) {
requestFocus(); // Get FOCUS on click
display.evnt[Display.MOUSE] =
"MOUSE clicked";
showMouse(e);
}
public void mousePressed(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE pressed";
showMouse(e);
}
public void mouseReleased(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE released";
showMouse(e);
}
public void mouseEntered(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE entered";
showMouse(e);
}
public void mouseExited(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE exited";
showMouse(e);
}
void showMouse(MouseEvent e) {
display.evnt[Display.MOUSE] +=
", x = " + e.getX() +
", y = " + e.getY();
repaint();
}
}
class MML implements MouseMotionListener {
public void mouseDragged(MouseEvent e) {
display.evnt[Display.MOUSE_MOVE] =
"MOUSE dragged";
showMouse(e);
}
public void mouseMoved(MouseEvent e) {
display.evnt[Display.MOUSE_MOVE] =
"MOUSE moved";
showMouse(e);
}
void showMouse(MouseEvent e) {
display.evnt[Display.MOUSE_MOVE] +=
", x = " + e.getX() +
", y = " + e.getY();
repaint();
}
}
}
class MyButton extends Button {
int clickCounter;
String label = "";
public MyButton() {
addActionListener(new AL());
}
public void paint(Graphics g) {
g.setColor(Color.green);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
g.setColor(Color.black);
g.drawRect(0, 0, s.width - 1, s.height - 1);
drawLabel(g);
}
private void drawLabel(Graphics g) {
FontMetrics fm = g.getFontMetrics();
int width = fm.stringWidth(label);
int height = fm.getHeight();
int ascent = fm.getAscent();
int leading = fm.getLeading();
int horizMargin =
(getSize().width - width)/2;
int verMargin =
(getSize().height - height)/2;
g.setColor(Color.red);
g.drawString(label, horizMargin,
verMargin + ascent + leading);
}
class AL implements ActionListener {
public void actionPerformed(ActionEvent e) {
clickCounter++;
label = "click #" + clickCounter +
" " + e.toString();
repaint();
}
}
}
public class GoodTechnique extends Frame {
GoodTechnique() {
setLayout(new GridLayout(2,2));
add(new EnabledPanel(1, Color.cyan));
add(new EnabledPanel(2, Color.lightGray));
add(new EnabledPanel(3, Color.yellow));
}
public static void main(String[] args) {
Frame f = new GoodTechnique();
f.setTitle("Good Technique");
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.out.println(e);
System.out.println("Window Closing");
System.exit(0);
}
});
f.setSize(700,700);
f.setVisible(true);
}
} ///:~
```
這個程序例子同樣證明了各種各樣的發現和顯示關于它們的信息的事件。這種顯示是一種集中顯示信息的方法。一組字符串去獲取關于每種類型的事件的信息,并且`show()`方法對任何圖像對象都設置了一個引用,我們采用并直接地寫在外觀代碼上。這種設計是有意的被某種事件重復使用。
激活面板代表了這種新型的組件。它是一個底部有一個按鈕的彩色的面板,并且它由利用接收器類為每一個單獨的事件來引發捕捉所有發生在它之上的事件,除了那些在激活面板重載的老式的`processEvent()`方法(注意它應該同樣調用`super.processEvent()`)。利用這種方法的唯一理由是它捕捉發生的每一個事件,因此我們可以觀察持續發生的每一事件。`processEvent()`方法沒有更多的展示代表每個事件的字符串,否則它會不得不使用一串條件語句去尋找事件。在其它方面,內嵌接收類早已清晰地知道被發現的事件。(假定我們注冊它們到組件,我們不需要任何的控件的邏輯,這將成為我們的目的。)因此,它們不會去檢查任何事件;這些事件正好做它們的原材料。
每個接收器修改顯示字符串和它的指定事件,并且調用重畫方法`repaint()`因此將顯示這個字符串。我們同樣能注意到一個通常能消除閃爍的秘訣:
```
public void update(Graphics g) {
paint(g);
}
```
我們不會始終需要重載`update()`,但如果我們寫下一些閃爍的程序,并運行它。默認的最新版本的清除背景然后調用`paint()`方法重新畫出一些圖畫。這個清除動作通常會產生閃爍,但是不必要的,因為`paint()`重畫了整個的外觀。
我們可以看到許多的接收器——但是,對接收器輸入檢查指令,但我們卻不能接收任何組件不支持的事件。(不像`BadTechnuque.java`那樣我們能時時刻刻看到)。
試驗這個程序是十分的有教育意義的,因為我們學習了許多的關于在Java中事件發生的方法。一則它展示了大多數開窗口的系統中設計上的瑕疵:它相當的難以去單擊和釋放鼠標,除非移動它,并且當我們實際上正試圖用鼠標單擊在某物體上時開窗口的會常常認為我們是在拖動。一個解決這個問題的方案是使用`mousePressed()`鼠標按下方法和`mouseReleased()`鼠標釋放方法去代替`mouseClicked()`鼠標單擊方法,然后判斷是否去調用我們自己的以時間和4個像素的鼠標滯后作用的“`mouseReallyClicked()`真實的鼠標單擊”方法。
(5) 蹩腳的組件繼承
另一種做法是調用`enableEvent()`方法,并將與希望控制的事件對應的模型傳遞給它(許多參考書中都曾提及這種做法)。這樣做會造成那些事件被發送至老式方法(盡管它們對Java 1.1來說是新的),并采用象`processFocusEvent()`這樣的名字。也必須要記住調用基類版本。下面是它看起來的樣子。
```
//: BadTechnique.java
// It's possible to override components this way,
// but the listener approach is much better, so
// why would you?
import java.awt.*;
import java.awt.event.*;
class Display {
public static final int
EVENT = 0, COMPONENT = 1,
MOUSE = 2, MOUSE_MOVE = 3,
FOCUS = 4, KEY = 5, ACTION = 6,
LAST = 7;
public String[] evnt;
Display() {
evnt = new String[LAST];
for(int i = 0; i < LAST; i++)
evnt[i] = new String();
}
public void show(Graphics g) {
for(int i = 0; i < LAST; i++)
g.drawString(evnt[i], 0, 10 * i + 10);
}
}
class EnabledPanel extends Panel {
Color c;
int id;
Display display = new Display();
public EnabledPanel(int i, Color mc) {
id = i;
c = mc;
setLayout(new BorderLayout());
add(new MyButton(), BorderLayout.SOUTH);
// Type checking is lost. You can enable and
// process events that the component doesn't
// capture:
enableEvents(
// Panel doesn't handle these:
AWTEvent.ACTION_EVENT_MASK |
AWTEvent.ADJUSTMENT_EVENT_MASK |
AWTEvent.ITEM_EVENT_MASK |
AWTEvent.TEXT_EVENT_MASK |
AWTEvent.WINDOW_EVENT_MASK |
// Panel can handle these:
AWTEvent.COMPONENT_EVENT_MASK |
AWTEvent.FOCUS_EVENT_MASK |
AWTEvent.KEY_EVENT_MASK |
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK |
AWTEvent.CONTAINER_EVENT_MASK);
// You can enable an event without
// overriding its process method.
}
// To eliminate flicker:
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
g.setColor(c);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
g.setColor(Color.black);
display.show(g);
}
public void processEvent(AWTEvent e) {
display.evnt[Display.EVENT]= e.toString();
repaint();
super.processEvent(e);
}
public void
processComponentEvent(ComponentEvent e) {
switch(e.getID()) {
case ComponentEvent.COMPONENT_MOVED:
display.evnt[Display.COMPONENT] =
"Component moved";
break;
case ComponentEvent.COMPONENT_RESIZED:
display.evnt[Display.COMPONENT] =
"Component resized";
break;
case ComponentEvent.COMPONENT_HIDDEN:
display.evnt[Display.COMPONENT] =
"Component hidden";
break;
case ComponentEvent.COMPONENT_SHOWN:
display.evnt[Display.COMPONENT] =
"Component shown";
break;
default:
}
repaint();
// Must always remember to call the "super"
// version of whatever you override:
super.processComponentEvent(e);
}
public void processFocusEvent(FocusEvent e) {
switch(e.getID()) {
case FocusEvent.FOCUS_GAINED:
display.evnt[Display.FOCUS] =
"FOCUS gained";
break;
case FocusEvent.FOCUS_LOST:
display.evnt[Display.FOCUS] =
"FOCUS lost";
break;
default:
}
repaint();
super.processFocusEvent(e);
}
public void processKeyEvent(KeyEvent e) {
switch(e.getID()) {
case KeyEvent.KEY_PRESSED:
display.evnt[Display.KEY] =
"KEY pressed: ";
break;
case KeyEvent.KEY_RELEASED:
display.evnt[Display.KEY] =
"KEY released: ";
break;
case KeyEvent.KEY_TYPED:
display.evnt[Display.KEY] =
"KEY typed: ";
break;
default:
}
int code = e.getKeyCode();
display.evnt[Display.KEY] +=
KeyEvent.getKeyText(code);
repaint();
super.processKeyEvent(e);
}
public void processMouseEvent(MouseEvent e) {
switch(e.getID()) {
case MouseEvent.MOUSE_CLICKED:
requestFocus(); // Get FOCUS on click
display.evnt[Display.MOUSE] =
"MOUSE clicked";
break;
case MouseEvent.MOUSE_PRESSED:
display.evnt[Display.MOUSE] =
"MOUSE pressed";
break;
case MouseEvent.MOUSE_RELEASED:
display.evnt[Display.MOUSE] =
"MOUSE released";
break;
case MouseEvent.MOUSE_ENTERED:
display.evnt[Display.MOUSE] =
"MOUSE entered";
break;
case MouseEvent.MOUSE_EXITED:
display.evnt[Display.MOUSE] =
"MOUSE exited";
break;
default:
}
display.evnt[Display.MOUSE] +=
", x = " + e.getX() +
", y = " + e.getY();
repaint();
super.processMouseEvent(e);
}
public void
processMouseMotionEvent(MouseEvent e) {
switch(e.getID()) {
case MouseEvent.MOUSE_DRAGGED:
display.evnt[Display.MOUSE_MOVE] =
"MOUSE dragged";
break;
case MouseEvent.MOUSE_MOVED:
display.evnt[Display.MOUSE_MOVE] =
"MOUSE moved";
break;
default:
}
display.evnt[Display.MOUSE_MOVE] +=
", x = " + e.getX() +
", y = " + e.getY();
repaint();
super.processMouseMotionEvent(e);
}
}
class MyButton extends Button {
int clickCounter;
String label = "";
public MyButton() {
enableEvents(AWTEvent.ACTION_EVENT_MASK);
}
public void paint(Graphics g) {
g.setColor(Color.green);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
g.setColor(Color.black);
g.drawRect(0, 0, s.width - 1, s.height - 1);
drawLabel(g);
}
private void drawLabel(Graphics g) {
FontMetrics fm = g.getFontMetrics();
int width = fm.stringWidth(label);
int height = fm.getHeight();
int ascent = fm.getAscent();
int leading = fm.getLeading();
int horizMargin =
(getSize().width - width)/2;
int verMargin =
(getSize().height - height)/2;
g.setColor(Color.red);
g.drawString(label, horizMargin,
verMargin + ascent + leading);
}
public void processActionEvent(ActionEvent e) {
clickCounter++;
label = "click #" + clickCounter +
" " + e.toString();
repaint();
super.processActionEvent(e);
}
}
public class BadTechnique extends Frame {
BadTechnique() {
setLayout(new GridLayout(2,2));
add(new EnabledPanel(1, Color.cyan));
add(new EnabledPanel(2, Color.lightGray));
add(new EnabledPanel(3, Color.yellow));
// You can also do it for Windows:
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
}
public void processWindowEvent(WindowEvent e) {
System.out.println(e);
if(e.getID() == WindowEvent.WINDOW_CLOSING) {
System.out.println("Window Closing");
System.exit(0);
}
}
public static void main(String[] args) {
Frame f = new BadTechnique();
f.setTitle("Bad Technique");
f.setSize(700,700);
f.setVisible(true);
}
} ///:~
```
的確,它能夠工作。但卻實在太蹩腳,而且很難編寫、閱讀、調試、維護以及復用。既然如此,為什么還不使用內部接收器類呢?
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物