作為一個 Java 程式員,從論壇上感受到使用 Java 開發程式的人越來多,心中不免欣慰。但是,同樣是從論壇中,看到多數人提到 Java 就以為是網絡開發——不是這樣的,Java 也可以開發應用程式,而且可以開發出漂亮的圖形使用者界面的應用程式,也就是 Windows/XWindow 應用程式。是以,我寫下這篇文章,希望能帶你進入 Java 圖形使用者界面設計之門。
下面,讓我們開始……
說明:所有代碼均在 Windows XP + Eclipse 環境下編寫和測試,示例圖檔均在 Windows XP 下捕捉。
目 錄
一. AWT 和 Swing
二. 架構、監聽器和事件
三. 按鈕、切換按鈕、複選按鈕和單選按鈕
四. 文本輸入框、密碼輸入框
五. 窗格、滾動窗格和布局管理
六. 後記——什麼是 SWT (2006年補充)
AWT 和 Swing 是 Java 設計 GUI 使用者界面的基礎。與 AWT 的重量級元件不同,Swing 中大部分是輕量級元件。正是這個原因,Swing 幾乎無所不能,不但有各式各樣先進的元件,而且更為美觀易用。是以一開始使用 AWT 的程式員很快就轉向使用 Swing 了。
那為什麼 AWT 元件沒有消亡呢?因為 Swing 是架構在 AWT 之上的,沒有 AWT 就沒有 Swing。是以程式員可以根據自己的習慣選擇使用 AWT 或者是 Swing。但是,最好不要二者混用——除開顯示風格不同不說,還很可能造成層次 (Z-Order) 錯亂,比如下例:
/*
* AwtSwing.java
* @author Fancy
*/
import java.awt.BorderLayout;
import java.awt.Button;
import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
public final class AwtSwing ...{
public static void main(String[] args) ...{
AwtSwing as = new AwtSwing();
as.show();
}
JFrame frame = new JFrame("Test AWT and SWING");
JDesktopPane jdp = new JDesktopPane();
JInternalFrame jif1 = new JInternalFrame("controls");
JInternalFrame jif2 = new JInternalFrame("cover");
public AwtSwing() ...{
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(jdp);
jif1.setContentPane(new JPanel());
jif2.setContentPane(new JPanel());
jif1.getContentPane().setLayout(new BorderLayout());
jif1.getContentPane().add(new Button("AWT Button"), BorderLayout.WEST);
jif1.getContentPane().add(new JButton("Swing Button"),
BorderLayout.EAST);
jif1.setSize(200, 100);
jif2.setSize(200, 100);
jdp.add(jif1);
jdp.add(jif2);
frame.setSize(240, 140);
public void show() ...{
frame.setVisible(true);
jif1.setVisible(true);
jif2.setVisible(true);
}
運作這個程式,并用滑鼠拖動那個名為“cover”的子視窗,我們會發現一個非常有趣的現象,如圖:
<a href="http://blog.51cto.com/attachment/201204/161722820.png" target="_blank"></a>
顯然 cover 子視窗是在 controls 子視窗之上的,但是它隻罩蓋住了 Swing Button,沒有罩蓋住 AWT Button。再看一會兒,你是不是有這樣一種感覺:Swing Button 是“畫”上去的,而 AWT Button 則是“貼”上去的。這就是二者混用造成層次錯亂的一個例子。
Swing 元件有美觀、易用、元件量大等特點,也有缺點——使用 Swing 元件的程式通常會比使用 AWT 元件的程式運作更慢。但是大家都還是更喜歡用 Swing 元件,原因何在?因為随着計算機硬體的更新,一點點速度已經不是問題。相反的,使用者更需要美觀的使用者界面,開發人員則更需要易用的開發元件。
架構 (Frame, JFrame) 是 Java 圖形使用者界面的基礎,它就是我們通常所說的視窗,是 Windows/XWindow 應用程式的典型特征。說到 Windows/XWindow,大家很容易聯想到“事件 (Event) 驅動”。Java 的圖形使用者界面正是事件驅動的,并且由各種各樣的監聽器 (Listener) 負責捕捉各種事件。
如果我們需要對某一個元件的某種事件進行捕捉和處理時,就需要為其添加監聽器。比如,我們要在一個視窗 (JFrame) 激活時改變它的标題,我們就需要為這個視窗 (JFrame 對象) 添加一個可以監聽到“激活視窗”這一事件的監聽器——WindowListener。
怎麼添加監聽器呢?這通常由元件類提供的一個 addXxxxxListener 的方法來完成。比如 JFrame 就提供有 addWindowListener 方法添加視窗監聽器 (WindowListener)。
一個監聽器常常不隻監聽一個事件,而是可以監聽相關的多個事件。比如 WindowListener 除了監聽視窗激活事件 (windowActivate) 之外,還可以監聽視窗關閉事件 (windowClosing) 等。那麼這些事件怎麼區分呢?就靠重載監聽器類 (Class) 的多個方法 (Method) 了。監聽器監聽到某個事件後,會自動調用相關的方法。是以我們隻要重載這個方法,就可以處理相應的事件了。
不妨先看一個例子:
/**
* @(#) TestFrame.java
* @author James
import javax.swing.*;
import java.awt.event.*;
public class TestFrame extends JFrame ...{
private int counter = 0;
public TestFrame() ...{
/**//* 使用匿名類添加一個視窗監聽器 */
addWindowListener(new WindowAdapter() ...{
public void windowClosing(WindowEvent e) ...{
System.out.println(
"Exit when Closed event");
//退出應用程式
System.exit(0);
}
public void windowActivated(WindowEvent e) ...{
// 改變視窗标題
setTitle("Test Frame " + counter++);
});
// 設定視窗為固定大小
setResizable(false);
setSize(200, 150);
TestFrame tf = new TestFrame();
tf.show();
這個例子中,我們設計了一個視窗類(public class TestFrame extends JFrame { ... }),并且為這個視窗添加了一個視窗監聽器 (addWindowListener(new WindowAdapter() ...)。而我們添加的這個視窗監聽器主要監聽了兩個事件:視窗關閉 (public void windowClosing(WindowEvent e) ...) 和視窗激活 (public void windowActivated(WindowEvent e) ...)。在視窗關閉事件中我們退出了整個應用程式(System.exit(0);),而在視窗激活事件中,我們改變了視窗的标題 (setTitle("Test Frame " + counter++);)。最後,我們在 main 方法中顯示了這視窗類的一個執行個體,運作得到下圖所示的結果:
<a href="http://blog.51cto.com/attachment/201204/161813312.png" target="_blank"></a>
這個程式的運作結果就是一個什麼東西都沒有加的架構,也就是一個空視窗。那麼,你知道顯示一個視窗最主要的幾句代碼嗎?不知道沒關系,我來告訴你,顯示一個視窗隻需要做三件事:生成執行個體(對象)→設定大小→顯示,相應的,就是下面的三句代碼:
JFrame frame = new JFrame("Frame's Title");
frame.setSize(400, 300);
frame.setVisible(true);
也許你會說:第一句的意思我清楚,第三句的意思我也明白,為什麼一定要第二句呢?其實想想也就明白了,叫你畫一個沒法有大小的矩形你能畫出來嗎?不能。同樣,沒有大小的視窗,怎麼顯示?是以我們需要用 setSize(int width, int height) 方法為其設定大小。我們還有另一種方法:用 JFrame 的 pack() 方法讓它自己适配一個大小。pack() 在多數時候是令人滿意的,但有時,它也會讓你哭笑不得——多試試就知道了。
在 JFrame 中,我們使用 addWindowListener 方法加入一個監聽器 WindowListener (addWindowListener(new WindowAdapter() ...) 去監聽發生在 JFrame 上的視窗事件。WindowListener 是一個接口,在 java.awt.event 這個包中,但是上例中好象并沒有使用 WindowListener,而是使用的 WindowsAdapter 吧,這是怎麼回事?
WindowAdapter 是 WindowsListener 接口的一個最簡單的實作,也在 java.awt.event 包中。如果我們直接使用 WindowListener 産生一個類,需要實作它的每一個方法 (一共 7 個)。但 WindowAdapter 作為 WindowListener 最簡單的實作,已經實作了它的每一個方法為空方法 (即隻包含空語句,或者說沒有語句的方法)。用 WindowAdapter 就隻需要重載可能用到的方法 (上例中隻有 2 個) 就行了,而不需要再去實作每一個方法。優點顯而易見——減少編碼量。
在 JFrame 上發生的視窗事件 (WindowEvent) 包括:
<a href="http://blog.51cto.com/attachment/201204/162231880.png" target="_blank"></a>
上例重載了其中兩個方法。如果在上例運作産生的視窗和另外一個應用程式視窗之間來回切換 (在 Windows 作業系統中你可以使用 Alt+Tab 進行切換)……試試看,你發現了什麼?有沒有現我們的示例視窗标題上的數字一直在增加,這便是在 windowActivated 事件中 setTitle("Test Frame " + counter++) 的功勞。
而另一個事件處理函數 windowClosing 中的 System.exit(0) 則保證了當視窗被關閉時退出目前的 Java 應用程式。如果不作這樣的處理會怎樣呢?試驗之後你會發現,視窗雖然關閉了,但程式并沒有結束,但此時,除了使用 Ctrl+C 強行結束之外,恐怕也沒有其它辦法了。是以,這一點非常重要:你想在關閉視窗的時候退出應用程式,那就需要處理 windowClosing 事件。……也不盡然,其實還有另外一個更簡單的辦法,讓 JFrame 自己處理這件事——你隻需要如下調用 JFrame 的 setDefaultCloseOperation 即可:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
在産生 JFrame 對象之後執行上述語句,就可以不用處理 windowsClosing 事件來退出程式了。
我們可以在 JFrame 對象中添加 AWT 或者 Swing 元件。但是,雖然它有 add 方法,卻不能直接用于添加元件,否則崤壯鲆斐!恍啪褪允浴T斐燒飧鱿窒蟮腦蛑揮幸桓鼋馐停篔Frame 不是一個容器,它隻是一個架構。那麼,應該怎麼添加元件呢?
JFrame 有一個 Content Pane,視窗是顯示的所有元件都是添加在這個 Content Pane 中。JFrame 提供了兩個方法:getContentPane 和 setContentPane 就是用于擷取和設定其 Content Pane 的。通常我們不需要重新設定 JFrame 的 Content Pane,隻需要直接擷取預設的 Content Pane 來添加元件等。如:(new JFrame()).getContentPane().add(new Button("test button"))。
按鈕……就是按鈕,不會連按鈕都不知道吧?
切換按鈕,有兩種狀态的按鈕,即按下狀态和彈起狀态,若稱為選擇狀态或未選擇狀态。
複選按鈕,又叫複選框,用一個小方框中是否打勾來表示兩種狀态。
單選按鈕,又叫收音機按鈕,以小圓框打點表示被選中。常成組出現,一組單選按鈕中隻有一個能被選中。
發現什麼了嗎?——對了,這一部分是在講各種各樣的按鈕,而且後三種按鈕都有兩種狀态。先看看這些按鈕都長成什麼樣:
<a href="http://blog.51cto.com/attachment/201204/162253568.png" target="_blank"></a>
上圖中,從上到下,依次就是按鈕、切換按鈕、複選按鈕和單選按鈕。圖示的視窗,就是下面這個例子的運作結果:
* TestButtons.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.JToggleButton;
public final class TestButtons ...{
TestButtons tb = new TestButtons();
tb.show();
JFrame frame = new JFrame("Test Buttons");
JButton jButton = new JButton("JButton"); // 按鈕
JToggleButton toggle = new JToggleButton("Toggle Button"); // 切換按鈕
JCheckBox checkBox = new JCheckBox("Check Box"); // 複選按鈕
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."); // 不是按鈕,是靜态文本
public TestButtons() ...{
frame.getContentPane().setLayout(new java.awt.FlowLayout());
// 為一般按鈕添加動作監聽器
jButton.addActionListener(new ActionListener() ...{
public void actionPerformed(ActionEvent ae) ...{
label.setText("You clicked jButton");
// 為切換按鈕添加動作監聽器
toggle.addActionListener(new ActionListener() ...{
JToggleButton toggle = (JToggleButton) ae.getSource();
if (toggle.isSelected()) ...{
label.setText("You selected Toggle Button");
} else ...{
label.setText("You deselected Toggle Button");
}
// 為複選按鈕添加條目監聽器
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();
// 生成一個新的動作監聽器對象,備用
ActionListener al = new ActionListener() ...{
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");
label.setText("You selected Radio Button 3");
};
// 為各單選按鈕添加動作監聽器
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);
除一般按鈕外,其餘三種按鈕都有兩種狀态,即選擇 (按下) 狀态和未選擇 (彈起) 狀态。那麼我們又該如何判斷呢?切換按鈕 (JToggleButton) 提供了一個 isSelected() 方法用來判斷目前所處的狀态,傳回值為真 (true) 時表示它處于選擇狀态,傳回值為假 (false) 時表示它處于未選擇狀态。而複選按鈕 (JCheckBox) 和單選按鈕 (JRadioButton) 都是從 JToggleButton 繼承的,是以也具有 isSelected() 方法。如上例中 if (toggle.isSelected()) { ... } 等。
單選按鈕由自身的特點決定了它們必須成組出現,而且一組中隻能有一個能被選中。是以我們需要用一個專門的類,ButtonGroup 來管理。添加到 ButtonGroup 的多個單選按鈕中,如果有一個被選擇中,同組中的其它單選按鈕都會自動改變其狀态為未選擇狀态。在 ButtonGroup 中添加按鈕,是使用它的 add 方法,如上例中的 group.add(radio1);。
既然我們已經将多個單選按鈕添加到一個 ButtonGroup 中了,那麼我們是不是可以将一個包含多個單選按鈕的 ButtonGroup 對象添加到 JFrame 的 Content Pane 中,以達到添加其中所有單選按鈕的目的呢?不行!ButtonGroup 不是一個可顯示的元件,它僅用于管理。是以,在往 JFrame 中添加一組 JRadioButton 的時候,需要一個一個的添加 JRadioButton,而不是籠統的添加一個 ButtonGroup。
上例中還用到了 JLabel,這不是按鈕,而是一個靜态文本元件,主要用于顯示提示文本。要獲得一個 JLabel 對象目前顯示的文本内容,可以使用它的 getText() 方法;反之,要改變一個 JLabel 對象顯示的文本,要使用它的 setText(String text) 方法,如上例中的 label.setText("You selected Toggle Button")。
其實這兩個方法同樣可以用于 JButton 等類。比如上例中我們使用 new JButton("JButton") 構造了一個按鈕 jButton,如果使用 jButton.getText() 就可以得到字元串 "JButton"。而 jButton.setText("A Button"),則可以改變按鈕上顯示的文字為 "A Button"。這兩句代碼沒有在示例中寫出來,你可以自己試試。
上例中大量使用了動作監聽器 (ActionListener)。ActionListener 隻監聽一個事件,這個事件在其相關元件上産生了動作時被觸發,是以叫作動作事件 (ActionEvent)。ActionListener 隻有一個方法需要實作,就是 actionPerformed(ActionEvent event)。按鈕、切換按鈕和單選按鈕被單擊時都會觸發動作事件,引起動作監聽器調用 actionPerformed 方法。是以,如果你想在單擊按鈕之後做什麼事,當然應該重載 ActionListener 的 actionPerformed 方法了。各種按鈕都提供了 addActionListener 方法以添加動作監聽器。
複選框就要特殊一些。雖然它也有 addActionListener 方法,意味着可以使用動作監聽器,但是使用之後你會發現動作監聽器并沒有起到預想的作用。為什麼?原來,單擊一個複選按鈕,觸發的不是動作事件,而是條目事件 (ItemEvent) 中的狀态變化 (itemStateChanged),由條目監聽器 (ItemListener) 監聽,相應需要重載的方法是 ItemListener 的 itemStateChanged 方法。
上例中我們将一個名為 al 的 ActionListener 添加到了每一個單選按鈕中,如何判斷是哪個單選按鈕觸發了事件并被 al 監聽到了呢?我們可以從 ActionEvent 的 getSource() 方法得到觸發事件單選按鈕。由于 getSource() 傳回的是一個 Object 引用,雖然這個引用指向的是一個單選按鈕的執行個體,但我們還是需要将這個引用的類型轉換為 JRadioButton,如上例中的:JRadioButton radio = (JRadioButton) ae.getSource(),隻有這樣我們才能調用 JRadioButton 有而 Object 沒有的方法。
同時,還需要說明的一點是,每個單選按鈕都可以添加一個單獨的 ActionListener 執行個體,而不一定要添加同一個。同樣的道理,若幹毫不相幹的、需要添加 ActionListener 的若幹元件,也可以添加同一個 ActionListener 執行個體。關鍵在于程式設計者對 actionPerformed 方法的重載。比如下面這段代碼就為一個 JButton 對象和一個 JRadioButton 對象添加了同一個動作監聽器執行個體:
* @(#) Test.java
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() ...{
if (ae.getSource() == b) ...{
System.out.println(
"You clicked the JButton");
"You clicked the RadioButton");
b.addActionListener(a);
rb.addActionListener(a);
f.getContentPane().add(b);
f.getContentPane().add(rb);
f.pack();
f.show();
new Test();
運作程式後,分别單擊兩個按鈕,相應的,在控制台能分别得到如下輸出:
You clicked the JButton
You clicked the RadioButton
這說明多個不用的元件添加同一個監聽器是可行的——不過前提是這些元件都能添加這個監聽器。
文本輸入框包括兩種,單行文本輸入框 (JTextField) 和多行文本輸入框 (JTextArea)。密碼輸入框則隻有一種 (JPasswordField)。JPasswordField 是 JTextField 的子類,它們的主要差別是 JPasswordField 不會顯示出使用者輸入的東西,而隻會顯示出程式員設定的一個固定字元,比如 '*' 或者 '#'。
下面的示例圖和代碼是 JTextField、JPasswordField 和 JTextArea 的示例:
<a href="http://blog.51cto.com/attachment/201204/162435990.png" target="_blank"></a>
* TestTexts.java
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
public final class TestTexts extends JFrame ...{
TestTexts tt = new TestTexts();
tt.setVisible(true);
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);
/**//* 監聽文本光标移動事件 */
textField.addCaretListener(new CaretListener() ...{
public void caretUpdate(CaretEvent e) ...{
// 如果改變了内容,就可以即時更新 label 顯示的内容
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);
上例中,我們構造了一個寬度為 15 個字元的單行文本框 (textField = new JTextField(15);),并使用 addCaretListener 方法添加了一個 CaretListener (textField.addCaretListener ...)。CaretListener 監聽文本光标的移動事件。當使用者使用鍵盤、滑鼠等移動了文本光标在 JTextField 中的位置時觸發這個事件。我們需要重載 caretUpdate(CaretEvent e) 對事件進行處理 (public void caretUpdate(CaretEvent e) ...)。這樣,我們可以在這裡做類似 VB 中 TextBox 的 OnChange 事件中做的事情。
JTextField 有 5 個構造方法,常用其中的四個:
JTextField()
JTextField(int columns),如上例 textField = new JTextField(15);
JTextField(String text)
JTextField(String text, int columns)
其中,參數 text 是單行文本框的初始内容,而 columns 指定了單行文本框的寬度,以字元為機關。JTextField 中的文本内容可以用 getText() 方法獲得。也可以用 setText 方法指定 JTextField 中的文本内容。
JPasswordField 是 JTextField 的子類,其構造方法也是類似的。JPasswordField 提供了 setEchoChar(char ch) 方法設定為了隐藏密碼而顯示的字元,預設為 '*' 字元,上例中則設定為了 '#' 字元 (pwdField.setEchoChar('#');)。與 JTextField 一樣,JPasswordField 也用 getText 方法和 setText 獲得或者設定文本内容 (當然在使用者界面上是隐藏的)。
JTextField 是單行文本框,不能顯示多行文本,如果想要顯示多行文本,就隻好使用多行文本框 JTextArea 了。JTextArea 有六個構造方法,常用的也是四個:
JTextArea()
JTextArea(int rows, int columns)
JTextArea(String text)
JTextArea(String text, int rows, int columns)
text 為 JTextArea 的初始化文本内容;rows 為 JTextArea 的高度,以行為機關;columns 為 JTextArea 的寬度,以字元為機關。如上例中就構造了一個高 5 行,寬 15 個字元的多行文本框 (textArea = new JTextArea(5, 15);)。
多行文本框預設是不會自動折行的 (不過可以輸入回車符換行),我們可以使用 JTextArea 的 setLineWrap 方法設定是否允許自動折行。setLineWrap(true) 是允許自動折行,setLineWrap(false) 則是不允許自動折行。多行文本框會根據使用者輸入的内容自動擴充大小,不信,自己做個實驗——如果不自動折行,那麼多行文本框的寬度由最長的一行文字确定的;如果行資料超過了預設的行數,則多行文本框會擴充自身的高度去适應。換句話說,多行文本框不會自動産生滾動條。怎麼辦?後面講到滾動窗格 (JScrollPane) 的時候,你就知道了。
多行文本框裡文本内容的獲得和設定,同樣可以使用 getText 和 setText 兩個方法來完成。
窗格 (JPanel) 和滾動窗格 (JScrollPane) 在圖形使用者界面設計中大量用于各種元件在視窗上的布置和安排。這裡所謂的布置和安排,就是布局 (Layout),是以不得不先說說布局。
将加入到容器(通常為視窗等) 的元件按照一定的順序和規則放置,使之看起來更美觀,這就是布局。布局由布局管理器 (Layout Manager) 來管理。那麼,我們在什麼時候應該使用布局管理器?應用選擇哪種布局管理器?又該怎樣使用布局管理器呢?
往往,我們設計一個視窗,其中是要添加若幹元件的。為了管理好這些管理的布局,我們就要使用布局管理器。比如說,設計一個簡單的編輯器,這個編輯器中隻需要放置兩個按鈕和一個多行文本框。這些元件是讓 Java 自己任意安排呢?還是按照一定的位置關系較規範的安排呢?當然應該選擇後者。那麼,為了按照一定的位置關系安排這些元件,我們就需要用到布局管理器了。
然後我們遇到了一個選擇題——使用哪種布局管理器。為此,我們首先要知道有些什麼布局管理器,它們的布局特點是什麼。常用的布局管理器有: FlowLayout、BorderLayout、GridLayout、BoxLayout 等,其中 FlowLayout 和 BorderLayout 最常用,本文主要也就隻談談這兩種布局管理器。下面清單說明它們的布局特點:
布局管理器 布局特點
<a href="http://blog.51cto.com/attachment/201204/162541541.png" target="_blank"></a>
就上述的編輯器為例,如果選用 FlowLayout,那麼兩個按鈕和一個多行文本框就會排列在一行——當然這是視窗足夠寬的情況;如果視窗稍窄一些,則可能分兩行排列,第一行有兩個按鈕,而第二行是多行文本框——這是最理想的情況;如果視窗再窄一些,就可能分三行排列,第一行和第二行分别放置一個按鈕,第三行放置多行文本框。是以,如果視窗大小可以改變,那麼三個元件的位置關系也可能随着視窗大小的變化而變化。其實上面所舉的例程中,大部分都是用的 FlowLayout,那是因為我們沒有要求元件的布局。
如果選用 BorderLayout 的情況又如何呢?我們可以試着加入一個窗格 (JPanel,稍後講解),并将兩個按鈕放置在其中,然後将這個窗格加入到 BorderLayout 的北部 (即上部);再将多行文本框加入到 BorderLayout 中部。結果類似使用 FlowLayout 的第二種可能,是最理想的情況。而且,如果改變視窗大小,它們的位置關系仍然是北-中的關系,不會随之改變。
剩下的兩種布局管理器,加以窗格 (JPanel) 的配合,也能夠很好的安排上述編輯器所需的三個元件。但是由于它們的使用稍為複雜一些,是以就不講了。下面就講講如何使用 FlowLayout 和 BorderLayout。
任何布局管理器,都需要用在容器上,比如 JFrame 的 Content Pane 和下面要說的 JPanel 都是容器(JFrame 預設的 Content Pane 實際就是一個 JPanel)。容器元件提供了一個 setLayout 方法,就是用來改變其布局管理器的。預設情況下,JFrame 的 Content Pane 使用的是 BorderLayout,而一個新産生的 JPanel 對象使用的是 FlowLayout。但不管怎樣,我們都可以調用它們的 setLayout 方法來改變其布局管理器。比如上述的編輯器中,我們要讓視窗 (JFrame 對象,假設為 frame) 使用 BorderLayout,就可以使用 frame.getContentPane().setLayout(new BorderLayout()); 來改變其布局管理器為一個新的 BorderLayout 對象。
然後,我們對布局管理器的直接操作就結束了,剩下的隻需要往容器裡添加元件。如果使用 FlowLayout,我們隻需要使用容器的 add(Component c) 方法添加元件就行了。但是,如果使用 BorderLayout 就不一樣了,因為要指定是把元件添加到哪個區域啊。那我們就使用容器的 add(Component c, Object o) 方法添加元件,該方法的第二個參數就是指明添加到的區域用的。例如,上述編輯器中要添加一個多行文本框到 BorderLayout 的中部,就可以用 frame.getContentPane().add(new JTextArea(5, 15), BorderLayout.CENTER) 來實作。
BorderLayout 的五個區域分别是用下列五個常量來描述的:
<a href="http://blog.51cto.com/attachment/201204/162625545.png" target="_blank"></a>
剛才已經提到了使用 JPanel。JPanel 作為一個容器,可以包容一些元件,然後将這個 JPanel 對象作為一個元件添加到另一個容器 (稱作父容器) 中。這個功能有什麼好處呢?
上面不是提到 BorderLayout 的一個區域中隻能添加一個元件嗎?但是我們的編輯器需要添加兩個按鈕到它的北部,怎麼辦?下面的例子中,我們就會用的一個 JPanel 包容了這兩個按鈕,然後再将這個 JPanel 對象作為一個元件添加到設定布局管理器為 BorderLayout 的 Content Pane 中。
上面說到各布局管理器的布局特點的時候,幾乎每一種都是一個區域隻能添加一個元件,那我們想添加多個元件到一個區域的時候,就要用到 JPanel 了。如果還沒有明白,稍後看一段程式可能更易于了解。
而滾動窗格 (JScrollPane) 呢?它是一個能夠自己産生滾動條的容器,通常隻包容一個元件,并且根據這個元件的大小自動産生滾動條。比如上面講 JTextArea 的時候提到:JTextAera 會随使用者輸入的内容自動擴充大小,很容易打破各元件的布局。但是,如果我們将它包容在一個滾動窗格中,它的擴充就不會直接反映在大小的變化上,而會反映在滾動窗格的滾動條上,也就不會打破各元件的布局了。稍後的例子會讓你清清楚楚。
是不是等着看例子了?好,例子來了:
* TestPanels.java
import javax.swing.JScrollPane;
public final class TestPanels extends JFrame ...{
TestPanels tp = new TestPanels();
tp.setVisible(true);
public TestPanels() ...{
JPanel panel = new JPanel();
for (int i = 0; i < 2; i++) ...{
panel.add(new JButton("Button 00" + i));
}
JTextArea textArea = new JTextArea(5, 15);
JScrollPane scrollPane = new JScrollPane(textArea);
getContentPane().add(panel, BorderLayout.NORTH);
getContentPane().add(scrollPane, BorderLayout.CENTER);
pack();
這個例子的運作結果如下圖,正是我們想要的結果——上面兩個按鈕,下面是一個可以滾動的多行文本框:
<a href="http://blog.51cto.com/attachment/201204/162734237.png" target="_blank"></a>
上例中首先産生了一個 JPanel 對象 (JPanel panel = new JPanel();),然後将兩個按鈕置于其中 (panel.add ...);然後産生了一個多行文本框 (JTextArea textArea = new JTextArea(5, 15);),并使用一個滾動窗格将它包裹起來 (JScrollPane scrollPane = new JScrollPane(textArea);),使之成為可以滾動的多行文本框。最後将兩個容器 (JPanel 對象和 JScrollPane 對象) 分别添加到了視窗的北部 (getContentPane().add(panel, BorderLayout.NORTH);) 和中部 (也就是剩餘部分,getContentPane().add(scrollPane, BorderLayout.CENTER);)。
好像有點不對勁,是什麼呢?對了,我們沒有設定 Content Pane 的布局管理器為 BorderLayout 啊,為什麼……剛才不是說了嗎,JFrame 的 Content Pane 的預設布局管理器就是 BorderLayout,是以不用再設定了。
好了,《簡述 Java 圖形使用者界面設計》就告一段落了。由于篇幅有限,這裡說的都是初級知識,有此基礎,設計複雜一點的圖形使用者界面也就不是難事了!
本文轉自邊城__ 51CTO部落格,原文連結:http://blog.51cto.com/jamesfancy/843257,如需轉載請自行聯系原作者