# 13.15 視窗化應用
出于安全的緣故,我們會看到在程序片我們的行為非常的受到限制。我們真實地感到,程序片是被臨時地加入在WEB瀏覽器中的,因此,它的功能連同它的相關知識,控件都必須加以限制。但是,我們希望Java能制造一個開窗口的程序去運行一些事物,否則寧愿安放在一個WEB頁面上,并且也許我們希望它可以運行一些可靠的應用程序,以及夸張的實時便攜性。在這本書前面的章節中我們制造了一些命令行應用程序,但在一些操作環境中(例如:Macintosh)沒有命令行。所以我們有很多的理由去利用Java創建一個設置窗口,非程序片的程序。這當然是一個十分合理的要求。
一個Java設置窗口應用程序可以擁有菜單和對話框(這對一個程序片來說是不可能的和很困難的),可是如果我們使用一個老版本的Java,我們將會犧牲本地操作系統環境的外觀和感受。JFC/Swing庫允許我們制造一個保持原來操作系統環境的外觀和感受的應用程序。如果我們想建立一個設置窗口應用程序,它會合理地運作,同樣,如果我們可以使用最新版本的Java并且集合所有的工具,我們就可以發布不會使用戶困惑的應用程序。如果因為一些原因,我們被迫使用老版本的Java,請在毀壞以建立重要的設置窗口的應用程序前仔細地考慮。
## 13.15.1 菜單
直接在程序片中安放一個菜單是不可能的(Java 1.0,Java1.1和Swing庫不允許),因為它們是針對應用程序的。繼續,如果您不相信我并且確定在程序片中可以合理地擁有菜單,那么您可以去試驗一下。程序片中沒有`setMenuBar()`方法,而這種方法是附在菜單中的(我們會看到它可以合理地在程序片產生一個幀,并且幀包含菜單)。
有四種不同類型的`MenuComponent`(菜單組件),所有的菜單組件起源于抽象類:菜單條(我們可以在一個事件幀里擁有一個菜單條),菜單去支配一個單獨的下拉菜單或者子菜單、菜單項來說明菜單里一個單個的元素,以及起源于`MenuItem`,產生檢查標志(`checkmark`)去顯示菜單項是否被選擇的`CheckBoxMenuItem`。
不同的系統使用不同的資源,對Java和AWT而言,我們必須在源代碼中手工匯編所有的菜單。
```
//: Menu1.java
// Menus work only with Frames.
// Shows submenus, checkbox menu items
// and swapping menus.
import java.awt.*;
public class Menu1 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 = {
new MenuItem("Open"),
new MenuItem("Exit")
};
// 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"),
};
Button b = new Button("Swap Menus");
public Menu1() {
for(int i = 0; i < flavors.length; i++) {
m.add(new MenuItem(flavors[i]));
// 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("Center", t);
// Set up the system for swapping menus:
add("North", b);
for(int i = 0; i < other.length; i++)
fooBar.add(other[i]);
mb2.add(fooBar);
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public boolean action(Event evt, Object arg) {
if(evt.target.equals(b)) {
MenuBar m = getMenuBar();
if(m == mb1) setMenuBar(mb2);
else if (m == mb2) setMenuBar(mb1);
}
else if(evt.target instanceof MenuItem) {
if(arg.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(evt.target.equals(file[1]))
System.exit(0);
// CheckboxMenuItems cannot use String
// matching; you must match the target:
else if(evt.target.equals(safety[0]))
t.setText("Guard the Ice Cream! " +
"Guarding is " + safety[0].getState());
else if(evt.target.equals(safety[1]))
t.setText("Hide the Ice Cream! " +
"Is it cold? " + safety[1].getState());
else
t.setText(arg.toString());
}
else
return super.action(evt, arg);
return true;
}
public static void main(String[] args) {
Menu1 f = new Menu1();
f.resize(300,200);
f.show();
}
} ///:~
```
在這個程序中,我避免了為每個菜單編寫典型的冗長的`add()`列表調用,因為那看起來像許多的無用的標志。取而代之的是,我安放菜單項到數組中,然后在一個`for`的循環中通過每個數組調用`add()`簡單地跳過。這樣的話,增加和減少菜單項變得沒那么討厭了。
作為一個可選擇的方法(我發現這很難令我滿意,因為它需要更多的分配)`CheckboxMenuItems`在數組的引用中被創建是被稱為安全創建;這對數組文件和其它的文件而言是真正的安全。
程序中創建了不是一個而是二個的菜單條來證明菜單條在程序運行時能被交換激活。我們可以看到菜單條怎樣組成菜單,每個菜單怎樣組成菜單項(`MenuItems`),`chenkboxMenuItems`或者其它的菜單(產生子菜單)。當菜單組合后,可以用`setMenuBar()`方法安裝到現在的程序中。值得注意的是當按鈕被壓下時,它將檢查當前的菜單安裝使用`getMenuBar()`,然后安放其它的菜單條在它的位置上。
當測試是`open`(即開始)時,注意拼寫和大寫,如果開始時沒有對象,Java發出`no error`(沒有錯誤)的信號。這種字符串比較是一個明顯的程序設計錯誤源。
校驗和非校驗的菜單項自動地運行,與之相關的`CheckBoxMenuItems`著實令人吃驚,這是因為一些原因它們不允許字符串匹配。(這似乎是自相矛盾的,盡管字符串匹配并不是一種很好的辦法。)因此,我們可以匹配一個目標對象而不是它們的標簽。當演示時,`getState()`方法用來顯示狀態。我們同樣可以用`setState()`改變`CheckboxMenuItem`的狀態。
我們可能會認為一個菜單可以合理地置入超過一個的菜單條中。這看似合理,因為所有我們忽略的菜單條的`add()`方法都是一個引用。然而,如果我們試圖這樣做,這個結果將會變得非常的別扭,而遠非我們所希望得到的結果。(很難知道這是一個編程中的錯誤或者說是他們試圖使它以這種方法去運行所產生的。)這個例子同樣向我們展示了為什么我們需要建立一個應用程序以替代程序片。(這是因為應用程序能支持菜單,而程序片是不能直接使用菜單的。)我們從幀處繼承代替從程序片處繼承。另外,我們為類建一個構造器以取代`init()`安裝事件。最后,我們創建一個`main()`方法并且在我們建的新型對象里,調整它的大小,然后調用`show()`。它與程序片只在很小的地方有不同之處,然而這時它已經是一個獨立的設置窗口應用程序并且我們可以使用菜單。
## 13.15.2 對話框
對話框是一個從其它窗口彈出的窗口。它的目的是處理一些特殊的爭議和它們的細節而不使原來的窗口陷入混亂之中。對話框大量在設置窗口的編程環境中使用,但就像前面提到的一樣,鮮于在程序片中使用。
我們需要從對話類處繼承以創建其它類型的窗口、像幀一樣的對話框。和窗框不同,對話框不能擁有菜單條也不能改變光標,但除此之外它們十分的相似。一個對話框擁有布局管理器(默認的是`BorderLayout`布局管理器)和重載`action()`等等,或用`handleEvent()`去處理事件。我們會注意到`handleEvent()`的一個重要差異:當`WINDOW_DESTORY`事件發生時,我們并不希望關閉正在運行的應用程序!
相反,我們可以使用對話窗口通過調用`dispace()`釋放資源。在下面的例子中,對話框是由定義在那兒作為類的`ToeButton`的特殊按鈕組成的網格構成的(利用`GridLayout`布局管理器)。`ToeButton`按鈕圍繞它自已畫了一個幀,并且依賴它的狀態:在空的中的`X`或者`O`。它從空白開始,然后依靠使用者的選擇,轉換成`X`或`O`。但是,當我們單擊在按鈕上時,它會在`X`和`O`之間來回交換。(這產生了一種類似填字游戲的感覺,當然比它更令人討厭。)另外,這個對話框可以被設置為在主應用程序窗口中為很多的行和列變更號碼。
```
//: ToeTest.java
// Demonstration of dialog boxes
// and creating your own components
import java.awt.*;
class ToeButton extends Canvas {
int state = ToeDialog.BLANK;
ToeDialog parent;
ToeButton(ToeDialog parent) {
this.parent = parent;
}
public void paint(Graphics g) {
int x1 = 0;
int y1 = 0;
int x2 = size().width - 1;
int y2 = size().height - 1;
g.drawRect(x1, y1, x2, y2);
x1 = x2/4;
y1 = y2/4;
int wide = x2/2;
int high = y2/2;
if(state == ToeDialog.XX) {
g.drawLine(x1, y1, x1 + wide, y1 + high);
g.drawLine(x1, y1 + high, x1 + wide, y1);
}
if(state == ToeDialog.OO) {
g.drawOval(x1, y1, x1+wide/2, y1+high/2);
}
}
public boolean
mouseDown(Event evt, int x, int y) {
if(state == ToeDialog.BLANK) {
state = parent.turn;
parent.turn= (parent.turn == ToeDialog.XX ?
ToeDialog.OO : ToeDialog.XX);
}
else
state = (state == ToeDialog.XX ?
ToeDialog.OO : ToeDialog.XX);
repaint();
return true;
}
}
class ToeDialog extends Dialog {
// w = number of cells wide
// h = number of cells high
static final int BLANK = 0;
static final int XX = 1;
static final int OO = 2;
int turn = XX; // Start with x's turn
public ToeDialog(Frame parent, int w, int h) {
super(parent, "The game itself", false);
setLayout(new GridLayout(w, h));
for(int i = 0; i < w * h; i++)
add(new ToeButton(this));
resize(w * 50, h * 50);
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
dispose();
else
return super.handleEvent(evt);
return true;
}
}
public class ToeTest extends Frame {
TextField rows = new TextField("3");
TextField cols = new TextField("3");
public ToeTest() {
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("North", p);
add("South", new Button("go"));
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public boolean action(Event evt, Object arg) {
if(arg.equals("go")) {
Dialog d = new ToeDialog(
this,
Integer.parseInt(rows.getText()),
Integer.parseInt(cols.getText()));
d.show();
}
else
return super.action(evt, arg);
return true;
}
public static void main(String[] args) {
Frame f = new ToeTest();
f.resize(200,100);
f.show();
}
} ///:~
```
`ToeButton`類保留了一個引用到它`ToeDialog`型的父類中。正如前面所述,`ToeButton`和`ToeDialog`高度的結合因為一個`ToeButton`只能被一個`ToeDialog`所使用,但它卻解決了一系列的問題,事實上這實在不是一個糟糕的解決方案因為沒有另外的可以記錄用戶選擇的對話類。當然我們可以使用其它的制造`ToeDialog.turn`(`ToeButton`的靜態的一部分)方法。這種方法消除了它們的緊密聯系,但卻阻止了我們一次擁有多個`ToeDialog`(無論如何,至少有一個正常地運行)。
`paint()`是一種與圖形有關的方法:它圍繞按鈕畫出矩形并畫出`X`或`O`。這完全是冗長的計算,但卻十分的直觀。
一個鼠標單擊被重載的`mouseDown()`方法所俘獲,最要緊的是檢查是否有事件寫在按鈕上。如果沒有,父窗口會被詢問以找出誰選擇了它并用來確定按鈕的狀態。值得注意的是按鈕隨后交回到父類中并且改變它的選擇。如果按鈕已經顯示這為`X`和`O`,那么它們會被改變狀態。我們能注意到本書第三章中描述的在這些計算中方便的使用的三個一組的`If-else`。當一個按鈕的狀態改變后,按鈕會被重畫。
`ToeDialog`的構造器十分的簡單:它像我們所需要的一樣增加一些按鈕到`GridLayout`布局管理器中,然后調整每個按鈕每邊大小為50個像素(如果我們不調整窗口,那么它就不會顯示出來)。注意`handleEvent()`正好為`WINDOW_DESTROY`調用`dispose()`,因此整個應用程序不會被關閉。
`ToeTest`設置整個應用程序以創建`TextField`(為輸入按鈕網格的行和列)和`go`按鈕。我們會領會`action()`在這個程序中使用不太令人滿意的“字符串匹配”技術來測試按鈕的按下(請確定我們拼寫和大寫都是正確的!)。當按鈕按下時,`TextField`中的數據將被取出,并且,因為它們在字符串結構中,所以需要利用靜態的`Integer.paresInt()`方法來轉變成中斷。一旦對話類被建立,我們就必須調用`show()`方法來顯示和激活它。
我們會注意到`ToeDialog`對象賦值給一個對話引用 `d`。這是一個向上轉換的例子,盡管它沒有真正地產生重要的差異,因為所有的事件都是`show()`調用的。但是,如果我們想調用`ToeDialog`中已經存在的一些方法,我們需要對`ToeDialog`引用賦值,就不會在一個上溯中丟失信息。
(1) 文件對話類
在一些操作系統中擁有許多的特殊內建對話框去處理選擇的事件,例如:字庫,顏色,打印機以及類似的事件。幾乎所有的操作系統都支持打開和保存文件,但是,Java的`FileDialog`包更容易使用。當然這會不再檢測所有使用的程序片,因為程序片在本地磁盤上既不能讀也不能寫文件。(這會在新的瀏覽器中交換程序片的信任關系。)
下面的應用程序運用了兩個文件對話類的窗體,一個是打開,一個是保存。大多數的代碼到如今已為我們所熟悉,而所有這些有趣的活動發生在兩個不同按鈕單擊事件的`action()`方法中。
```
//: FileDialogTest.java
// Demonstration of File dialog boxes
import java.awt.*;
public class FileDialogTest extends Frame {
TextField filename = new TextField();
TextField directory = new TextField();
Button open = new Button("Open");
Button save = new Button("Save");
public FileDialogTest() {
setTitle("File Dialog Test");
Panel p = new Panel();
p.setLayout(new FlowLayout());
p.add(open);
p.add(save);
add("South", p);
directory.setEditable(false);
filename.setEditable(false);
p = new Panel();
p.setLayout(new GridLayout(2,1));
p.add(filename);
p.add(directory);
add("North", p);
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public boolean action(Event evt, Object arg) {
if(evt.target.equals(open)) {
// Two arguments, defaults to open file:
FileDialog d = new FileDialog(this,
"What file do you want to open?");
d.setFile("*.java"); // Filename filter
d.setDirectory("."); // Current directory
d.show();
String openFile;
if((openFile = d.getFile()) != null) {
filename.setText(openFile);
directory.setText(d.getDirectory());
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
}
else if(evt.target.equals(save)) {
FileDialog d = new FileDialog(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("");
}
}
else
return super.action(evt, arg);
return true;
}
public static void main(String[] args) {
Frame f = new FileDialogTest();
f.resize(250,110);
f.show();
}
} ///:~
```
對一個“打開文件”對話框,我們使用構造器設置兩個參數;首先是父窗口引用,其次是`FileDialog`標題條的標題。`setFile()`方法提供一個初始文件名--也許本地操作系統支持通配符,因此在這個例子中所有的`.java`文件最開頭會被顯示出來。`setDirectory()`方法選擇文件決定開始的目錄(一般而言,操作系統允許用戶改變目錄)。
`show()`命令直到對話類關閉才返回。`FileDialog`對象一直存在,因此我們可以從它那里讀取數據。如果我們調用`getFile()`并且它返回空,這意味著用戶退出了對話類。文件名和調用`getDirectory()`方法的結果都顯示在`TextFields`里。
按鈕的保存工作使用同樣的方法,除了因為`FileDialog`而使用不同的構造器。這個構造器設置了三個參數并且第三的一個參數必須為`FileDialog.SAVE`或`FileDialog.OPEN`。
- 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 推薦讀物