# 13.13 `action`的替代品
正如早先指出的那樣,`action()`并不是我們對所有事進行分類后自動為`handleEvent()`調用的唯一方法。有三個其它的被調用的方法集,如果我們想捕捉某些類型的事件(鍵盤、鼠標和焦點事件),因此我們不得不重載規定的方法。這些方法是定義在基類組件里,所以他們幾乎在所有我們可能安放在窗體中的組件中都是有用的。然而,我們也注意到這種方法在Java 1.1版中是不被支持的,同樣盡管我們可能注意到繼承代碼利用了這種方法,我們將會使用Java 1.1版的方法來代替(本章后面有詳細介紹)。
| 組件方法 | 何時調用 |
| --- | --- |
| `action(Event evt, Object what)` | 當典型的事件針對組件發生(例如,當按下一個按鈕或下拉列表項目被選中)時調用 |
| `keyDown(Event evt, int key)` | 當按鍵被按下,組件擁有焦點時調用。第二個參數是按下的鍵并且是冗余的是從evt.key處復制來的 |
| `keyup(Event evt, int key)` | 當按鍵被釋放,組件擁有焦點時調用 |
| `lostFocus(Event evt, Object what)` | 焦點從目標處移開時調用。通常,`what`是從`evt.arg`里冗余復制的 |
| `gotFocus(Event evt, Object what)` | 焦點移動到目標時調用 |
| `mouseDown(Event evt, int x,int y)` | 一個鼠標按下存在于組件之上,在X,Y座標處時調用 |
| `mouseUp(Event evt, int x, int y)` | 一個鼠標升起存在于組件之上時調用 |
| `mouseMove(Event evt, int x, int y)` | 當鼠標在組件上移動時調用 |
| `mouseDrag(Event evt, int x, int y)` | 鼠標在一次`mouseDown`事件發生后拖動。所有拖動事件都會報告給內部發生了`mouseDown`事件的那個組件,直到遇到一次`mouseUp`為止 |
| `mouseEnter(Event evt, int x, int y)` | 鼠標從前不在組件上方,但目前在 |
| `mouseExit(Event evt, int x, int y)` | 鼠標曾經位于組件上方,但目前不在 |
當我們處理特殊情況時——一個鼠標事件,例如,它恰好是我們想得到的鼠標事件存在的座標,我們將看到每個程序接收一個事件連同一些我們所需要的信息。有趣的是,當組件的`handleEvent()`調用這些方法時(典型的事例),附加的參數總是多余的因為它們包含在事件對象里。事實上,如果我們觀察`component.handleEvent()`的源代碼,我們能發現它顯然將增加的參數抽出事件對象(這可能是考慮到在一些語言中無效率的編碼,但請記住Java的焦點是安全的,不必擔心。)試驗對我們表明這些事件事實上在被調用并且作為一個有趣的嘗試是值得創建一個重載每個方法的程序片,(`action()`的重載在本章的其它地方)當事件發生時顯示它們的相關數據。
這個例子同樣向我們展示了怎樣制造自己的按鈕對象,因為它是作為目標的所有事件權益來使用。我可能會首先(也是必須的)假設制造一個新的按鈕,我們從按鈕處繼承。但它并不能運行。取而代之的是,我們從畫布組件處(一個非常普通組件)繼承,并在其上不使用`paint()`方法畫出一個按鈕。正如我們所看到的,自從一些代碼混入到畫按鈕中去,按鈕根本就不運行,這實在是太糟糕了。(如果您不相信我,試圖在例子中為畫布組件交換按鈕,請記住調用稱為`super`的基類構造器。我們會看到按鈕不會被畫出,事件也不會被處理。)
`myButton`類是明確說明的:它只和一個自動事件(`AutoEvent`)“父窗口”一起運行(父窗口不是一個基類,它是按鈕創建和存在的窗口。)。通過這個知識,`myButton`可能進入到父窗口并且處理它的文字字段,必然就能將狀態信息寫入到父窗口的字段里。當然這是一種非常有限的解決方法,`myButton`僅能在連結`AutoEvent`時被使用。這種代碼有時稱為“高度結合”。但是,制造`myButton`更需要很多的不是為例子(和可能為我們將寫的一些程序片)擔保的努力。再者,請注意下面的代碼使用了Java 1.1版不支持的API。
```
//: AutoEvent.java
// Alternatives to action()
import java.awt.*;
import java.applet.*;
import java.util.*;
class MyButton extends Canvas {
AutoEvent parent;
Color color;
String label;
MyButton(AutoEvent parent,
Color color, String label) {
this.label = label;
this.parent = parent;
this.color = color;
}
public void paint(Graphics g) {
g.setColor(color);
int rnd = 30;
g.fillRoundRect(0, 0, size().width,
size().height, rnd, rnd);
g.setColor(Color.black);
g.drawRoundRect(0, 0, size().width,
size().height, rnd, rnd);
FontMetrics fm = g.getFontMetrics();
int width = fm.stringWidth(label);
int height = fm.getHeight();
int ascent = fm.getAscent();
int leading = fm.getLeading();
int horizMargin = (size().width - width)/2;
int verMargin = (size().height - height)/2;
g.setColor(Color.white);
g.drawString(label, horizMargin,
verMargin + ascent + leading);
}
public boolean keyDown(Event evt, int key) {
TextField t =
(TextField)parent.h.get("keyDown");
t.setText(evt.toString());
return true;
}
public boolean keyUp(Event evt, int key) {
TextField t =
(TextField)parent.h.get("keyUp");
t.setText(evt.toString());
return true;
}
public boolean lostFocus(Event evt, Object w) {
TextField t =
(TextField)parent.h.get("lostFocus");
t.setText(evt.toString());
return true;
}
public boolean gotFocus(Event evt, Object w) {
TextField t =
(TextField)parent.h.get("gotFocus");
t.setText(evt.toString());
return true;
}
public boolean
mouseDown(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseDown");
t.setText(evt.toString());
return true;
}
public boolean
mouseDrag(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseDrag");
t.setText(evt.toString());
return true;
}
public boolean
mouseEnter(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseEnter");
t.setText(evt.toString());
return true;
}
public boolean
mouseExit(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseExit");
t.setText(evt.toString());
return true;
}
public boolean
mouseMove(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseMove");
t.setText(evt.toString());
return true;
}
public boolean mouseUp(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseUp");
t.setText(evt.toString());
return true;
}
}
public class AutoEvent extends Applet {
Hashtable h = new Hashtable();
String[] event = {
"keyDown", "keyUp", "lostFocus",
"gotFocus", "mouseDown", "mouseUp",
"mouseMove", "mouseDrag", "mouseEnter",
"mouseExit"
};
MyButton
b1 = new MyButton(this, Color.blue, "test1"),
b2 = new MyButton(this, Color.red, "test2");
public void init() {
setLayout(new GridLayout(event.length+1,2));
for(int i = 0; i < event.length; i++) {
TextField t = new TextField();
t.setEditable(false);
add(new Label(event[i], Label.CENTER));
add(t);
h.put(event[i], t);
}
add(b1);
add(b2);
}
} ///:~
```
我們可以看到構造器使用利用參數同名的方法,所以參數被賦值,并且使用`this`來區分:
```
this.label = label;
```
`paint()`方法由簡單的開始:它用按鈕的顏色填充了一個“圓角矩形”,然后畫了一個黑線圍繞它。請注意`size()`的使用決定了組件的寬度和長度(當然,是像素)。這之后,`paint()`看起來非常的復雜,因為有大量的預測去計算出怎樣利用“font metrics”集中按鈕的標簽到按鈕里。我們能得到一個相當好的關于繼續關注方法調用的主意,它將程序中那些相當平凡的代碼挑出,當我們想集中一個標簽到一些組件里時,我們正好可以對它進行剪切和粘貼。
您直到注意到`AutoEvent`類才能正確地理解`keyDown()`,`keyUp()`及其它方法的運行。這包含一個`Hashtable`(譯者注:散列表)去控制字符串來描述關于事件處理的事件和`TextField`類型。當然,這些能被靜態的創建而不是放入`Hashtable`但我認為您會同意它是更容易使用和改變的。特別是,如果我們需要在`AutoEvent`中增加或刪除一個新的事件類型,我們只需要簡單地在事件列隊中增加或刪除一個字符串——所有的工作都自動地完成了。
我們查出在`keyDown()`,`keyup()`及其它方法中的字符串的位置回到`myButton`中。這些方法中的任何一個都用父引用試圖回到父窗口。父類是一個`AutoEvent`,它包含`Hashtable h`和`get()`方法,當擁有特定的字符串時,將對一個我們知道的`TextField`對象產生一個引用(因此它被選派到那)。然后事件對象修改顯示在`TextField`中的字符串陳述。從我們可以真正注意到舉出的例子在我們的程序中運行事件時以來,可以發現這個例子運行起來頗為有趣的。
- 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 推薦讀物