對話框是一個從其它窗口彈出的窗口。它的目的是處理一些特殊的爭議和它們的細節而不使原來的窗口陷入混亂之中。對話框大量在設置窗口的編程環境中使用,但就像前面提到的一樣,鮮於在程序片中使用。
我們需要從對話類處繼承以創建其它類型的窗口、像幀一樣的對話框。和窗框不同,對話框不能擁有菜單條也不能改變光標,但除此之外它們十分的相似。一個對話框擁有布局管理器(默認的是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。