作為一個 Java 程序員,,從論壇上感受到使用 Java 開發(fā)程序的人越來多,心中不免欣慰,。但是,,同樣是從論壇中,看到多數(shù)人提到 Java 就以為是網(wǎng)絡(luò)開發(fā)——不是這樣的,,Java 也可以開發(fā)應(yīng)用程序,,而且可以開發(fā)出漂亮的圖形用戶界面的應(yīng)用程序,也就是 Windows/XWindow 應(yīng)用程序,。因此,,我寫下這篇文章,希望能帶你進(jìn)入Java 圖形用戶界面設(shè)計(jì)之門,。
一. AWT 和 SWING
AWT 和 SWING 是 Java 設(shè)計(jì) GUI 用戶界面的基礎(chǔ),。與 AWT 的重量級組件不同,Swing 中大部分是輕量級組件,。正是這個原因,,Swing 幾乎無所不能,不但有各式各樣先進(jìn)的組件,,而且更為美觀易用,。所以一開始使用 AWT 的程序員很快就轉(zhuǎn)向使用 Swing 了,。 那為什么 AWT 組件沒有消亡呢,?因?yàn)?Swing 是架構(gòu)在 AWT 之上的,沒有 AWT 就沒有 Swing,。所以程序員可以根據(jù)自己的習(xí)慣選擇使用 AWT 或者是 Swing,。但是,最好不要二者混用——除開顯示風(fēng)格不同不說,還很可能造成層次 (Z-Order) 錯亂,,比如下例: /** * TestPanels.java * @author Fancy */ import javax.swing.*; import java.awt.*; public class TestPanels extends JFrame { public TestPanels() { setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); for (int i = 0; i < 2; i++) { panel.add(new JButton("Button 00" + i)); } JTextArea textArea = new JTextArea(5, 15); textArea.setLineWrap(true); JScrollPane scrollPane = new JScrollPane(textArea); getContentPane().add(panel, BorderLayout.NORTH); getContentPane().add(scrollPane, BorderLayout.CENTER); pack(); } public static void main(String[] args) { TestPanels tp = new TestPanels(); tp.show(); } } 運(yùn)行這個程序,,并用鼠標(biāo)拖動那個名為“cover”的子窗口,我們會發(fā)現(xiàn)一個非常有趣的現(xiàn)象,,如圖: 顯然 cover 子窗口是在 controls 子窗口之上的,,但是它只罩蓋住了 Swing Button,沒有罩蓋住 AWT Button,。再看一會兒,,你是不是有這樣一種感覺:Swing Button 是“畫”上去的,而 AWT Button 則是“貼”上去的,。這就是二者混用造成層次錯亂的一個例子,。 Swing 組件有美觀、易用,、組件量大等特點(diǎn),,也有缺點(diǎn)——使用 Swing 組件的程序通常會比使用 AWT 組件的程序運(yùn)行更慢。但是大家都還是更喜歡用 Swing 組件,,原因何在,?因?yàn)殡S著計(jì)算機(jī)硬件的升級,一點(diǎn)點(diǎn)速度已經(jīng)不是問題,。相反的,,用戶更需要美觀的用戶界面,開發(fā)人員則更需要易用的開發(fā)組件,。 ——好,,我這就來教你使用 Swing 組件開發(fā)圖形用戶界面的 Java 應(yīng)用程序。 二. 框架,、監(jiān)聽器和事件 框架 (Frame) 是 Java 圖形用戶界面的基礎(chǔ),,它就是我們通常所說的窗口,是 Windows/XWindow 應(yīng)用程序的典型特征,。說到 Windows/XWindow,,大家很輕易聯(lián)想到“事件 (Event) 驅(qū)動”。Java 的圖形用戶界面正是事件驅(qū)動的,,并且由各種各樣的監(jiān)聽器 (Listener) 負(fù)責(zé)捕捉各種事件,。 假如我們需要對某一個組件的某種事件進(jìn)行捕捉和處理時,就需要為其添加監(jiān)聽器,。比如,,我們要在一個窗口 (JFrame) 激活時改變它的標(biāo)題,我們就需要為這個窗口 (JFrame 對象) 添加一個可以監(jiān)聽到“激活窗口”這一事件的監(jiān)聽器——WindowListener,。 怎么添加監(jiān)聽器呢,?這通常由組件類提供的一個 addXXXXXListener 的方法來完成,。比如 JFrame 就提供有 addWindowListener 方法添加窗口監(jiān)聽器 (WindowListener)。 一個監(jiān)聽器經(jīng)常不只監(jiān)聽一個事件,,而是可以監(jiān)聽相關(guān)的多個事件,。比如 WindowListener 除了監(jiān)聽窗口激活事件 (windowActivate) 之外,還可以監(jiān)聽窗口關(guān)閉事件 (windowClosing) 等,。那么這些事件怎么區(qū)分呢,?就靠重載監(jiān)聽器類 (Class) 的多個方法 (Method) 了,監(jiān)聽器監(jiān)聽到某個事件后,,會自動調(diào)用相關(guān)的方法,。我們只要重載這個方法,就可以處理相應(yīng)的事件了,。 不妨先看一個例子: /** * TestFrame.java * @author Fancy */ import javax.swing.*; import java.awt.event.*; public class TestFrame extends JFrame { private int counter = 0; public TestFrame() { /* 使用匿名類添加一個窗口監(jiān)聽器 */ addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.out.println("Exit when Closed event"); System.exit(0); //退出應(yīng)用程序 } public void windowActivated(WindowEvent e) { setTitle("Test Frame " + counter++); // 改變窗口標(biāo)題 } }); setResizable(false); // 設(shè)置窗口為固定大小 setSize(200, 150); } public static void main(String[] args) { TestFrame tf = new TestFrame(); tf.show(); } } 這個例子中,,我們設(shè)計(jì)了一個窗口類(public class TestFrame extends JFrame { ...),并且為這個窗口添加了一個窗口監(jiān)聽器 (addWindowListener(new WindowAdapter() ...),。而我們添加的這個窗口監(jiān)聽器主要監(jiān)聽了兩個事件:窗口關(guān)閉 (public void windowClosing(WindowEvent e) ...) 和窗口激活 (public void windowActivated(WindowEvent e) ...),。在窗口關(guān)閉事件中我們退出了整個應(yīng)用程序(System.exit(0);),而在窗口激活事件中,,我們改變了窗口的標(biāo)題 (setTitle("Test Frame " + counter++);),。最后,我們在 main 方法中顯示了這窗口類的一個實(shí)例,,運(yùn)行得到下圖所示的結(jié)果: 這個程序的運(yùn)行結(jié)果就是一個什么東西都沒有加的框架,,也就是一個空窗口。那么,,你知道顯示一個窗口最主要的幾句代碼嗎,?不知道沒關(guān)系,我來告訴你,,顯示一個窗口只需要做三件事:生成實(shí)例(對象) -> 設(shè)置大小 -> 顯示,,相應(yīng)的,就是下面的三句代碼: JFrame frame = new JFrame("Frame‘s Title"); frame.setSize(400, 300); frame.show(); 也許你會說:第一句的意思我清楚,,第三句的意思我也明白,,為什么一定要第二句呢?其實(shí)想想也就明白了,,叫你畫一個沒法有大小的矩形你能畫出來嗎,?不能。同樣,,沒有大小的窗口,,怎么顯示?所以我們需要用 setSize(int width, int height) 方法為其設(shè)置大小,。我們還有另一種方法:用 JFrame 的 pack() 方法讓它自己適配一個大小,。pack() 在多數(shù)時候是令人滿足的,但有時,,它也會讓你哭笑不得——多試試就知道了,。 在 JFrame 中,我們使用 addWindowListener 方法加入一個監(jiān)聽器 WindowListener (addWindowListener(new WindowAdapter() ...) 去監(jiān)聽發(fā)生在 JFrame 上的窗口事件,。WindowListener 是一個接口,,在 java.awt.event 這個包中,但是上例中好象并沒有使用 WindowListener,,而是使用的 WindowsAdapter 吧,,這是怎么回事? WindowAdapter 是 WindowsListener 接口的一個最簡單的實(shí)現(xiàn),,也在包 java.awt.event 中,。假如我們直接使用 WindowListener 產(chǎn)生一個匿名類,需要實(shí)現(xiàn)它的每一個方法 (一共 7 個),。但 WindowAdapter 作為 WindowListener 最簡單的實(shí)現(xiàn),,已經(jīng)實(shí)現(xiàn)了它的每一個方法為空方法 (即只包含空語句,或者說沒有語句的方法),。用 WindowAdapter 就只需要重載可能用到的方法 (上例中只有 2 個) 就行了,,而不需要再去實(shí)現(xiàn)每一個方法。優(yōu)點(diǎn)顯而易見——減少代碼量,。 在 JFrame 上發(fā)生的窗口事件 (WindowEvent) 包括: windowActivated(WindowEvent e) 窗口得到焦點(diǎn)時觸發(fā) windowClosed(WindowEvent e) 窗口關(guān)閉之后觸發(fā) windowClosing(WindowEvent e) 窗口關(guān)閉時觸發(fā) windowDeactivated(WindowEvent e) 窗口失去焦點(diǎn)時觸發(fā) windowDeiconified(WindowEvent e) windowIconified(WindowEvent e) windowOpened(WindowEvent e) 窗口打開之后觸發(fā) 上例重載了其中兩個方法,。假如在上例運(yùn)行產(chǎn)生的窗口和另外一個應(yīng)用程序窗口之間往返切換 (在 Windows 操作系統(tǒng)中你可以使用 Alt+Tab 進(jìn)行切換)……試試看,你發(fā)現(xiàn)了什么,?有沒有現(xiàn)我們的示例窗口標(biāo)題上的數(shù)字一直在增加,,這便是在 windowActivated 事件中 setTitle("Test Frame " + counter++); 的功勞。 而另一個事件處理函數(shù) windowClosing 中的 System.exit(0) 則保證了當(dāng)窗口被關(guān)閉時退出當(dāng)前的 Java 應(yīng)用程序,。假如不作這樣的處理會怎樣呢,?試驗(yàn)之后你會發(fā)現(xiàn),窗口雖然關(guān)閉了,,但程序并沒有結(jié)束,,但此時,除了使用 ^C 強(qiáng)行結(jié)束之外,,恐怕也沒有其它辦法了,。所以,這一點(diǎn)非常重要:假如你想在關(guān)閉窗口的時候退出應(yīng)用程序,,需要你自己寫代碼處理 windowClosing 事件,。……也不盡然,其實(shí)還有另外一個更簡單的辦法,,讓 JFrame 自己處理這件事——你只需要如下調(diào)用 JFrame 的 setDefaultCloseOperation 即可: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 我們可以在 JFrame 對象中添加 AWT 或者 Swing 組件,。但是,,雖然它有 add 方法,卻不能直接用于添加組件,,否則會拋出異?!恍啪驮囋嚒T斐蛇@個現(xiàn)象的原因只有一個解釋:JFrame 不是一個容器,,它只是一個框架,。那么,應(yīng)該怎么添加組件呢,? JFrame 有一個 Content Pane,,窗口是顯示的所有組件都是添加在這個 Content Pane 中。JFrame 提供了兩個方法:getContentPane 和 setContentPane 就是用于獲取和設(shè)置其 Content Pane 的,。通常我們不需要重新設(shè)置 JFrame 的 Content Pane,,只需要直接獲取這個 Content Pane 來添加組件等。如:(new JFrame()).getContentPane().add(new Button("test button")); 三. 按鈕,、切換按鈕,、復(fù)選按鈕和單選按鈕 按鈕,就是按鈕,,不會連按鈕都不知道吧,? 切換按鈕,有兩種狀態(tài)的按鈕,,即按下狀態(tài)和彈起狀態(tài),,若稱為選擇狀態(tài)或未選擇狀態(tài)。 復(fù)選按鈕,,又叫復(fù)選框,,用一個小方框中是否打勾來表示兩種狀態(tài)。 單選按鈕,,又叫收音機(jī)按鈕,,以小圓框打點(diǎn)表示被選中。常成組出現(xiàn),,一組單選按鈕中只有一個能被選中,。 發(fā)現(xiàn)什么了嗎?——對了,,這一部分是在講各種各樣的按鈕,,而且后三種按鈕都有兩種狀態(tài)。先看看這些按鈕都長成什么樣: 上圖中,,從上到下,,依次就是按鈕、切換按鈕,、復(fù)選按鈕和單選按鈕,。圖示的窗口,,就是下面這個例子的運(yùn)行結(jié)果: /** * TestButtons.java * @author Fancy */ import javax.swing.*; import java.awt.event.*; public class TestButtons { JFrame frame = new JFrame("Test Buttons"); JButton jButton = new JButton("JButton"); //按鈕 JToggleButton toggle = new JToggleButton("Toggle Button"); //切換按鈕 JCheckBox checkBox = new JCheckBox("Check Box"); //復(fù)選按鈕 JRadioButton radio1 = new JRadioButton("Radio Button 1"); //單選按鈕 JRadioButton radio2 = new JRadioButton("Radio Button 2"); JRadioButton radio3 = new JRadioButton("Radio Button 3"); JLabel label = new JLabel("Here is Status, look here."); //不是按鈕,是靜態(tài)文本 public TestButtons() { frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().setLayout(new java.awt.FlowLayout()); /* 為一般按鈕添加動作監(jiān)聽器 */ jButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { label.setText("You clicked jButton"); } }); /* 為切換按鈕添加動作監(jiān)聽器 */ toggle.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { JToggleButton toggle = (JToggleButton) ae.getSource(); if (toggle.isSelected()) { label.setText("You selected Toggle Button"); } else { label.setText("You deselected Toggle Button"); } } }); /* 為復(fù)選按鈕添加條目監(jiān)聽器 */ checkBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { JCheckBox cb = (JCheckBox) e.getSource(); label.setText("Selected Check Box is " + cb.isSelected()); } }); /* 用一個按鈕組對象包容一組單選按鈕 */ ButtonGroup group = new ButtonGroup(); /* 生成一個新的動作監(jiān)聽器對象,,備用 */ ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent ae) { JRadioButton radio = (JRadioButton) ae.getSource(); if (radio == radio1) { label.setText("You selected Radio Button 1"); } else if (radio == radio2) { label.setText("You selected Radio Button 2"); } else { label.setText("You selected Radio Button 3"); } } }; /* 為各單選按鈕添加動作監(jiān)聽器 */ radio1.addActionListener(al); radio2.addActionListener(al); radio3.addActionListener(al); /* 將單選按鈕添加到按鈕組中 */ group.add(radio1); group.add(radio2); group.add(radio3); frame.getContentPane().add(jButton); frame.getContentPane().add(toggle); frame.getContentPane().add(checkBox); frame.getContentPane().add(radio1); frame.getContentPane().add(radio2); frame.getContentPane().add(radio3); frame.getContentPane().add(label); frame.setSize(200, 250); } public void show() { frame.show(); } public static void main(String[] args) { TestButtons tb = new TestButtons(); tb.show(); } } 除一般按鈕外,,其余三種按鈕都有兩種狀態(tài),即選擇 (按下) 狀態(tài)和未選擇 (彈起) 狀態(tài),。那么我們又該如何判定呢,?切換按鈕 (JToggleButton) 提供了一個 isSelected() 方法用來判定當(dāng)前所處的狀態(tài),,返回值為真 (true) 時表示它處于選擇狀態(tài),,返回值為假 (false) 時表示它處于未選擇狀態(tài)。而復(fù)選按鈕 (JCheckBox) 和單選按鈕 (JRadioButton) 都是從 JToggleButton 繼續(xù)的,,所以也具有 isSelected() 方法,。如上例中 if (toggle.isSelected()) { ... 等。 單選按鈕由自身的特點(diǎn)決定了它們必須成組出現(xiàn),,而且一組中只能有一個能被選中,。因此我們需要用一個專門的類——ButtonGroup——來治理。添加到 ButtonGroup 的多個單選按鈕中,,假如有一個被選擇中,,同組中的其它單選按鈕都會自動改變其狀態(tài)為未選擇狀態(tài)。在 ButtonGroup 中添加按鈕,,是使用它的 add 方法,,如上例中的 group.add(radio1);。 既然我們已經(jīng)將多個單選按鈕添加到一個 ButtonGroup 中了,,那么我們是不是可以將一個包含多個單選按鈕的 ButtonGroup 對象添加到 JFrame 的 Content Pane 中,,以達(dá)到添加其中所有單選按鈕的目的呢?不行,!ButtonGroup 不是一個可顯示的組件,,它僅用于治理。所以,,在往 JFrame 中添加一組 JRadioButton 的時候,,需要一個一個的添加 JRadioButton,而不是籠統(tǒng)的添加一個 ButtonGroup,。 上例中還用到了 JLabel,,這不是按鈕,而是一個靜態(tài)文本組件,,主要用于顯示提示文本,。要獲得一個 JLabel 對象當(dāng)前顯示的文本內(nèi)容,可以使用它的 getText() 方法,;反之,,要改變一個 JLabel 對象顯示的文本內(nèi)容,,應(yīng)該使用它的 setText(String text) 方法,如上例中的 label.setText("You selected Toggle Button");,。 其實(shí)這兩個方法同樣可以用于 JButton 等類,。比如上例中我們使用 new JButton("JButton") 構(gòu)造了一個按鈕 jButton,假如使用 jButton.getText() 就可以得到字符串 "JButton",。而 jButton.setText("A Button"),,則可以改變按鈕上顯示的文字為 "A Button"。這兩句代碼沒有寫出來,,你可以自己試試,。 上例中大量使用了動作監(jiān)聽器 (ActionListener)。ActionListener 只監(jiān)聽一個事件,,這個事件在其相關(guān)組件上產(chǎn)生了動作時被觸發(fā),,因此叫作動作事件 (ActionEvent)。ActionListener 只有一個方法需要實(shí)現(xiàn),,就是 actionPerformed(ActionEvent ae),。按鈕、切換按鈕和單選按鈕被單擊時都會觸發(fā)動作事件,,引起動作監(jiān)聽器調(diào)用 actionPerformed 方法,。因此,假如你想在單擊按鈕之后做什么事,,當(dāng)然應(yīng)該重載 ActionListener 的 actionPerformed 方法了,。各種按鈕都提供了 addActionListener 方法以添加動作監(jiān)聽器。 復(fù)選框就要非凡一些,。雖然它也有 addActionListener 方法,,意味著可以使用動作監(jiān)聽器,但是使用之后你會發(fā)現(xiàn)動作監(jiān)聽器并沒有起到預(yù)想的作用,。為什么,?原來,單擊一個復(fù)選按鈕,,觸發(fā)的不是動作事件,,而是條目事件 (ItemEvent) 中的狀態(tài)變化 (itemStateChanged) 事件,由條目監(jiān)聽器 (ItemListener) 監(jiān)聽,,相應(yīng)需要重載的方法是 ItemListener 的 itemStateChanged 方法,。 上例中我們將一個名為 al 的 ActionListener 添加到了每一個單選按鈕中,如何判定是哪個單選按鈕觸發(fā)了事件并被 al 監(jiān)聽到了呢,?我們可以從 ActionEvent 的 getSource() 方法得到觸發(fā)事件單選按鈕,。由于 getSource() 返回的是一個 Object 引用,雖然這個引用指向的是一個單選按鈕的實(shí)例,但我們還是需要將這個引用的類型轉(zhuǎn)換為 JRadioButton,,如上例中的:JRadioButton radio = (JRadioButton) ae.getSource();,,只有這樣我們才能調(diào)用 JRadioButton 有而 Object 沒有的方法。 同時,,還需要說明的一點(diǎn)是,,每個單選按鈕都可以添加一個單獨(dú)的 ActionListener 實(shí)例,而不一定要添加同一個,。同樣的道理,,若干毫不相干的、需要添加 ActionListener 的若干組件,,也可以添加同一個 ActionListener 實(shí)例,。要害在于編程者對 actionPerformed 方法的重載。比如下面這段代碼就為一個 JButton 對象和一個 JRadioButton 對象添加了同一個動作監(jiān)聽器實(shí)例: /** * Test.java * @author Fancy */ import javax.swing.*; import java.awt.event.*; public class Test { JButton b; JRadioButton rb; public Test() { JFrame f = new JFrame("Test"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().setLayout(new java.awt.FlowLayout()); b = new JButton("JButton"); rb = new JRadioButton("RadioButton"); ActionListener a = new ActionListener() { public void actionPerformed(ActionEvent ae) { if (ae.getSource() == b) { System.out.println("You clicked the JButton"); } else { System.out.println("You clicked the RadioButton"); } } }; b.addActionListener(a); rb.addActionListener(a); f.getContentPane().add(b); f.getContentPane().add(rb); f.pack(); f.show(); } public static void main(String[] args) { new Test(); } } 運(yùn)行程序后,,分別單擊兩個按鈕,,相應(yīng)的,,在控制臺能分別得到如下輸出: You clicked the JButton You clicked the RadioButton 這說明多個不用的組件添加同一個監(jiān)聽器是可行的——不過前提是這些組件都能添加這個監(jiān)聽器,。 四. 文本輸入框、密碼輸入框 文本輸入框包括兩種,,單行文本輸入框 (JTextField) 和多行文本輸入框 (JTextArea),。密碼輸入框則只有一種 (JPassWordField)。JPasswordField 是 JTextField 的子類,,它們的主要區(qū)別是 JPasswordField 不會顯示出用戶輸入的東西,,而只會顯示出程序員設(shè)定的一個固定字符,比如 ‘*‘,。 下面的示例圖和代碼是 JTextField,、JPasswordField 和 JTextArea 的示例: /** * TestTexts.java * @author Fancy */ import javax.swing.*; import javax.swing.event.*; public class TestTexts extends JFrame { private JLabel label = new JLabel("Status"); private JTextField textField; private JPasswordField pwdField; private JTextArea textArea; public TestTexts() { super("Test Texts"); setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().setLayout(new java.awt.FlowLayout()); textField = new JTextField(15); /* 監(jiān)聽文本光標(biāo)移動事件 */ textField.addCaretListener(new CaretListener() { public void caretUpdate(CaretEvent e) { // 假如改變了內(nèi)容,就可以即時更新 label 顯示的內(nèi)容 label.setText(textField.getText()); } }); pwdField = new JPasswordField(15); pwdField.setEchoChar(‘#‘); textArea = new JTextArea(5, 15); textArea.setLineWrap(true); getContentPane().add(textField); getContentPane().add(pwdField); getContentPane().add(textArea); getContentPane().add(label); setSize(200, 200); } public static void main(String[] args) { TestTexts tt = new TestTexts(); tt.show(); } } 上例中,,我們構(gòu)造了一個寬度為 15 個字符的單行文本框 (textField = new JTextField(15);),,并使用 addCaretListener 方法添加了一個 CaretListener (textField.addCaretListener ...)。CaretListener 監(jiān)聽文本光標(biāo)的移動事件,。當(dāng)用戶使用鍵盤,、鼠標(biāo)等移動了文本光標(biāo)在 JTextField 中的位置時觸發(fā)這個事件。我們需要重載 caretUpdate(CaretEvent e) 對事件進(jìn)行處理 (public void caretUpdate(CaretEvent e) ...),。這樣,,我們可以在這里做類似 VB 中 TextBox 的 OnChange 事件中做的事情。 JTextField 有 5 個構(gòu)造方法,,常用其中的四個: JTextField() JTextField(int columns),,如上例 textField = new JTextField(15); JTextField(String text) JTextField(String text, int columns) 其中,參數(shù) text 是單行文本框的初始內(nèi)容,而 columns 指定了單行文本框的寬度,,以字符為單位,。JTextField 中的文本內(nèi)容可以用 getText() 方法獲得。也可以用 setText 方法指定 JTextField 中的文本內(nèi)容,。 JPasswordField 是 JTextField 的子類,,其構(gòu)造方法也是類似的。JPasswordField 提供了 setEchoChar(char ch) 方法設(shè)置為了隱藏密碼而顯示的字符,,默認(rèn)為 ‘*‘ 字符,,上例中則設(shè)置為了 ‘#‘ 字符 (pwdField.setEchoChar(‘#‘);)。與 JTextField 一樣,,JPasswordField 也用 getText 方法和 setText 獲得或者設(shè)置文本內(nèi)容 (當(dāng)然在用戶界面上是隱藏的),。 JTextField 是單行文本框,不能顯示多行文本,,假如想要顯示多行文本,,就只好使用多行文本框 JTextArea 了。JTextArea 有六個構(gòu)造方法,,常用的也是四個: JTextArea() JTextArea(int rows, int columns) JTextArea(String text) JTextArea(String text, int rows, int columns) text 為 JTextArea 的初始化文本內(nèi)容,;rows 為 JTextArea 的高度,以行為單位,;columns 為 JTextArea 的寬度,,以字符為單位。如上例中就構(gòu)造了一個高 5 行,,寬 15 個字符的多行文本框 (textArea = new JTextArea(5, 15);),。 多行文本框默認(rèn)是不會自動折行的 (不過可以輸入回車符換行),我們可以使用 JTextArea 的 setLineWrap 方法設(shè)置是否答應(yīng)自動折行,。setLineWrap(true) 是答應(yīng)自動折行,,setLineWrap(false) 則是不答應(yīng)自動折行。多行文本框會根據(jù)用戶輸入的內(nèi)容自動擴(kuò)展大小,,不信,,自己做個實(shí)驗(yàn)——假如不自動折行,那么多行文本框的寬度由最長的一行文字確定的,;假如行數(shù)據(jù)超過了預(yù)設(shè)的行數(shù),,則多行文本框會擴(kuò)展自身的高度去適應(yīng)。換句話說,,多行文本框不會自動產(chǎn)生滾動條,。怎么辦?后面講到滾動窗格 (JScrollPane) 的時候,,你就知道了,。 多行文本框里文本內(nèi)容的獲得和設(shè)置,同樣可以使用 getText 和 setText 兩個方法來完成。 五. 窗格、滾動窗格和布局治理 窗格 (JPanel) 和滾動窗格 (JScrollPane) 在圖形用戶界面設(shè)計(jì)中大量用于各種組件在窗口上的布置和安排。這里所謂的布置和安排,,就是布局 (Layout),,因此不得不先說說布局,。 將加入到容器(通常為窗口等) 的組件按照一定的順序和規(guī)則放置,使之看起來更美觀,這就是布局。布局由布局治理器 (Layout Manager) 來治理,。那么,我們在什么時候應(yīng)該使用布局治理器,?應(yīng)用選擇哪種布局治理器,?又該怎樣使用布局治理器呢? 往往,,我們設(shè)計(jì)一個窗口,,其中是要添加若干組件的。為了治理好這些治理的布局,,我們就要使用布局治理器,。比如說,設(shè)計(jì)一個簡單的編輯器,,這個編輯器中只需要放置兩個按鈕和一個多行文本框,。這些組件是讓 Java 自己任意安排呢,?還是按照一定的位置關(guān)系較規(guī)范的安排呢,?當(dāng)然應(yīng)該選擇后者。那么,,為了按照一定的位置關(guān)系安排這些組件,,我們就需要用到布局治理器了。 然后我們碰到了一個選擇題——使用哪種布局治理器,。為此,,我們首先要知道有些什么布局治理器,它們的布局特點(diǎn)是什么,。常用的布局治理器有: FlowLayout,、BorderLayout、GridLayout,、BoxLayout 等,,其中 FlowLayout 和 BorderLayout 最常用,本文主要也就只談?wù)勥@兩種布局治理器,。下面列表說明它們的布局特點(diǎn): 布局治理器布局特點(diǎn) FlowLayout 將組件按從左到右從上到下的順序依次排列,,一行不能放完則折到下一行繼續(xù)放置 BorderLayout 將組件按東、南、西,、北,、中五個區(qū)域放置,每個方向最多只能放置一個組件 GridLayout 形似一個無框線的表格,,每個單元格中放一個組件 BoxLayout 就像整潔放置的一行或者一列盒子,,每個盒子中一個組件 就上述的編輯器為例,假如選用 FlowLayout,,那么兩個按鈕和一個多行文本框就會排列在一行——當(dāng)然這是窗口足夠?qū)挼那闆r,;假如窗口稍窄一些,則可能分兩行排列,,第一行有兩個按鈕,,而第二行是多行文本框——這是最理想的情況;假如窗口再窄一些,,就可能分三行排列,,第一行和第二行分別放置一個按鈕,第三行放置多行文本框,。因此,,假如窗口大小可以改變,那么三個組件的位置關(guān)系也可能隨著窗口大小的變化而變化,。所以,,F(xiàn)lowLayout 不適用。其實(shí)上面所舉的例程中,,大部分都是用的 FlowLayout,,那是因?yàn)槲覀儧]有要求組件的布局。 假如選用 BorderLayout 的情況又如何呢,?我們可以試著加入一個窗格 (JPanel,,稍后講解),并將兩個按鈕放置在其中,,然后將這個窗格加入到 BorderLayout 的北部 (即上部),;再將多行文本框加入到 BorderLayout 中部。結(jié)果類似使用 FlowLayout 的第二種可能,,是最理想的情況,。而且,假如改變窗口大小,,它們的位置關(guān)系仍然是北-中的關(guān)系,,不會隨之改變。 剩下的兩種布局治理器,,加以窗格 (JPanel) 的配合,,也能夠很好的安排上述編輯器所需的三個組件,。但是由于它們的使用稍為復(fù)雜一些,所以就不講了,。下面就講講如何使用 FlowLayout 和 BorderLayout,。 任何布局治理器,都需要用在容器上,,比如 JFrame 的 Content Pane 和下面要說的 JPanel 都是容器,。容器組件提供了一個 setLayout 方法,就是用來改變其布局治理器的,。默認(rèn)情況下,,JFrame 的 Content Pane 使用的是 BorderLayout,而 JPanel 使用的是 FlowLayout,。但不管怎樣,,我們都可以調(diào)用它們的 setLayout 方法來改變其布局治理器。比如上述的編輯器中,,我們要讓窗口 (JFrame 對象,,假設(shè)為 frame) 使用 BorderLayout,就可以使用 frame.getContentPane().setLayout(new BorderLayout()); 來改變其布局治理器為一個新的 BorderLayout 對象,。 然后,,我們對布局治理器的直接操作就結(jié)束了,剩下的只需要往容器里添加組件,。假如使用 FlowLayout,,我們只需要使用容器的 add(Component c) 方法添加組件就行了。但是,,假如使用 BorderLayout 就不一樣了,,因?yàn)橐付ㄊ前呀M件添加到哪個區(qū)域啊。那我們就使用容器的 add(Component c, Object o) 方法添加組件,,該方法的第二個參數(shù)就是指明添加到的區(qū)域用的,。例如,上述編輯器中要添加一個多行文本框到 BorderLayout 的中部,,就可以用 frame.getContentPane().add(new JTextArea(5, 15), BorderLayout.CENTER) 來實(shí)現(xiàn)。 BorderLayout 的五個區(qū)域分別是用下列五個常量來描述的: BorderLayout.EAST 東 BorderLayout.SOUTH 南 BorderLayout.WEST 西 BorderLayout.NORTH 北 BorderLayout.CENTER 中 剛才已經(jīng)提到了使用 JPanel,。JPanel 作為一個容器,,可以包容一些組件,然后將這個 JPanel 對象作為一個組件添加到另一個容器 (稱作父容器) 中,。這個功能有什么好處呢,? 上面不是提到 BorderLayout 的一個區(qū)域中只能添加一個組件嗎?但是我們的編輯器需要添加兩個按鈕到它的北部,,怎么辦,?上面的例子中,,我們就是用的一個 JPanel 包容了這兩個按鈕,然后再將這個 JPanel 對象作為一個組件添加到設(shè)置布局治理器為 BorderLayout 的 Content Pane 中,。 上面說到各布局治理器的布局特點(diǎn)的時候,,幾乎每一種都是一個區(qū)域只能添加一個組件,那我們想添加多個組件到一個區(qū)域的時候,,就要用到 JPanel 了,。假如還沒有明白,稍后看一段程序可能更易于理解,。 而滾動窗格 (JScrollPane) 呢,?它是一個能夠自己產(chǎn)生滾動條的容器,通常只包容一個組件,,并且根據(jù)這個組件的大小自動產(chǎn)生滾動條,。比如上面講 JTextArea 的時候提到:JTextAera 會隨用戶輸入的內(nèi)容自動擴(kuò)展大小,很輕易打破各組件的布局,。但是,,假如我們將它包容在一個滾動窗格中,它的擴(kuò)展就不會直接反映在大小的變化上,,而會反映在滾動窗格的滾動條上,,也就不會打破各組件的布局了。稍后的例子會讓你清清楚楚,。 是不是等著看例子了,?好,例子來了: /** * TestPanels.java * @author Fancy */ import javax.swing.*; import java.awt.*; public class TestPanels extends JFrame { public TestPanels() { setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); for (int i = 0; i < 2; i++) { panel.add(new JButton("Button 00" + i)); } JTextArea textArea = new JTextArea(5, 15); textArea.setLineWrap(true); JScrollPane scrollPane = new JScrollPane(textArea); getContentPane().add(panel, BorderLayout.NORTH); getContentPane().add(scrollPane, BorderLayout.CENTER); pack(); } public static void main(String[] args) { TestPanels tp = new TestPanels(); tp.show(); } } 這個例子的運(yùn)行結(jié)果如下圖,,正是我們想要的結(jié)果——上面兩個按鈕,,下面是一個可以滾動的多行文本框: 上例中首先產(chǎn)生了一個 JPanel 對象 (JPanel panel = new JPanel();),然后將兩個按鈕置于其中 (panel.add ...),;然后產(chǎn)生了一個多行文本框 (JTextArea textArea = new JTextArea(5, 15);),,并使用一個滾動窗格將它包裹起來 (JScrollPane scrollPane = new JScrollPane(textArea);),使之成為可以滾動的多行文本框,。最后將兩個容器 (JPanel 對象和 JScrollPane 對象) 分別添加到了窗口的北部 (getContentPane().add(panel, BorderLayout.NORTH);) 和中部 (也就是剩余部分,,getContentPane().add(scrollPane, BorderLayout.CENTER);)。 似乎有點(diǎn)不對勁,,是什么呢,?對了,我們沒有設(shè)置 Content Pane 的布局治理器為 BorderLayout 啊,,為什么……剛才不是說了嗎,,JFrame 的 Content Pane 的默認(rèn)布局治理器就是 BorderLayout,所以不用再設(shè)置了,。 好了,,《Java 的圖形用戶界面設(shè)計(jì)》就告一段落了,。由于篇幅有限,這里說的都是初級知識,,有此基礎(chǔ),,設(shè)計(jì)復(fù)雜一點(diǎn)的圖形用戶界面也就不是難事了! |
|