天天看點

十一:Java之GUI圖形Awt和Swing

一. AWT和 Swing

 AWT 和 Swing 是 Java 設計 GUI使用者界面的基礎。與 AWT 的重量級元件不同,Swing 中大部分是輕量級元件。正是這個原因,Swing 幾乎無所不能,不但有各式各樣先進的元件,而且更為美觀易用。是以一開始使用AWT 的程式員很快就轉向使用 Swing 了。

  那為什麼 AWT元件沒有消亡呢?因為 Swing 是架構在 AWT 之上的,沒有 AWT 就沒有Swing。是以程式員可以根據自己的習慣選擇使用 AWT 或者是 Swing。但是,最好不要二者混用——除開顯示風格不同不說,還很可能造成層次 (Z-Order) 錯亂,比如下例: 

<span style="font-size:18px;">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);
    }

}</span>
           

  運作這個程式,并用滑鼠拖動那個名為“cover”的子視窗,我們會發現一個非常有趣的現象

  顯然 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) 了。監聽器監聽到某個事件後,會自動調用相關的方法。是以我們隻要重載這個方法,就可以處理相應的事件了。

  不妨先看一個例子: 

<span style="font-size:18px;">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);
    }

    public static void main(String[] args) {
        TestFrame tf = new TestFrame();
        tf.show();
    }

}</span>
           

  這個例子中,我們設計了一個視窗類(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 方法中顯示了這視窗類的一個執行個體

  這個程式的運作結果就是一個什麼東西都沒有加的架構,也就是一個空視窗。那麼,你知道顯示一個視窗最主要的幾句代碼嗎?不知道沒關系,我來告訴你,顯示一個視窗隻需要做三件事:生成執行個體(對象)→設定大小→顯示,相應的,就是下面的三句代碼:

        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) 包括:

windowActivated(WindowEvent e) 視窗得到焦點時觸發
windowClosed(WindowEvent e) 視窗關閉之後觸發
windowClosing(WindowEvent e) 視窗關閉時觸發
windowDeactivated(WindowEvent e) 視窗失去焦點時觸發
windowDeiconified(WindowEvent e)
windowIconified(WindowEvent e)
windowOpened(WindowEvent e) 視窗打開之後觸發

  上例重載了其中兩個方法。如果在上例運作産生的視窗和另外一個應用程式視窗之間來回切換 (在 Windows 作業系統中你可以使用 Alt+Tab 進行切換)……試試看,你發現了什麼?有沒有現我們的示例視窗标題上的數字一直在增加,這便是在 windowActivated 事件中 setTitle("TestFrame " + 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("testbutton"))。

三.按鈕、切換按鈕、複選按鈕和單選按鈕

  按鈕……就是按鈕,不會連按鈕都不知道吧?

  切換按鈕,有兩種狀态的按鈕,即按下狀态和彈起狀态,若稱為選擇狀态或未選擇狀态。

  複選按鈕,又叫複選框,用一個小方框中是否打勾來表示兩種狀态。

  單選按鈕,又叫收音機按鈕,以小圓框打點表示被選中。常成組出現,一組單選按鈕中隻有一個能被選中。

  發現什麼了嗎?——對了,這一部分是在講各種各樣的按鈕,而且後三種按鈕都有兩種狀态。先看看這些按鈕都長成什麼樣:

  上圖中,從上到下,依次就是按鈕、切換按鈕、複選按鈕和單選按鈕。圖示的視窗,就是下面這個例子的運作結果:

<span style="font-size:18px;">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.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.JToggleButton;

public final class TestButtons {

    public static void main(String[] args) {
        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.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        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() {
            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");
                }
            }
        });

        // 為複選按鈕添加條目監聽器
        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() {
            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");
                }
            }
        };
        // 為各單選按鈕添加動作監聽器
        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.setVisible(true);
    }

}</span>
           

  除一般按鈕外,其餘三種按鈕都有兩種狀态,即選擇 (按下) 狀态和未選擇 (彈起) 狀态。那麼我們又該如何判斷呢?切換按鈕 (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等類。比如上例中我們使用 newJButton("JButton") 構造了一個按鈕 jButton,如果使用 jButton.getText() 就可以得到字元串"JButton"。而 jButton.setText("AButton"),則可以改變按鈕上顯示的文字為 "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 對象添加了同一個動作監聽器執行個體:

<span style="font-size:18px;">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();
    }

}</span>
           

  運作程式後,分别單擊兩個按鈕,相應的,在控制台能分别得到如下輸出:

  You clicked the JButton

  You clicked the RadioButton

  這說明多個不用的元件添加同一個監聽器是可行的——不過前提是這些元件都能添加這個監聽器。

四.文本輸入框、密碼輸入框

  文本輸入框包括兩種,單行文本輸入框 (JTextField)和多行文本輸入框 (JTextArea)。密碼輸入框則隻有一種 (JPasswordField)。JPasswordField 是 JTextField 的子類,它們的主要差別是 JPasswordField 不會顯示出使用者輸入的東西,而隻會顯示出程式員設定的一個固定字元,比如 '*' 或者 '#'。

  下面的示例圖和代碼是 JTextField、JPasswordField 和 JTextArea 的示例:

<span style="font-size:18px;">import javax.swing.JFrame;
import javax.swing.JLabel;
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 {

    public static void main(String[] args) {
        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);
    }

}</span>
           

  上例中,我們構造了一個寬度為 15個字元的單行文本框 (textField = newJTextField(15);),并使用 addCaretListener 方法添加了一個 CaretListener (textField.addCaretListener ...)。CaretListener 監聽文本光标的移動事件。當使用者使用鍵盤、滑鼠等移動了文本光标在 JTextField 中的位置時觸發這個事件。我們需要重載caretUpdate(CaretEvent e) 對事件進行處理 (public voidcaretUpdate(CaretEvent e) ...)。這樣,我們可以在這裡做類似 VB 中 TextBox 的 OnChange 事件中做的事情。

  JTextField有 5 個構造方法,常用其中的四個:

  JTextField()

  JTextField(int columns),如上例 textField = newJTextField(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 最常用,本文主要也就隻談談這兩種布局管理器。下面清單說明它們的布局特點:

布局管理器布局特點

FlowLayout 将元件按從左到右從上到下的順序依次排列,一行不能放完則折到下一行繼續放置
BorderLayout 将元件按東(右)、南(下)、西(左)、北(上)、中五個區域放置,每個方向最多隻能放置一個元件(或容器)。
GridLayout 形似一個無框線的表格,每個單元格中放一個元件
BoxLayout 就像整齊放置的一行或者一列盒子,每個盒子中一個元件

  就上述的編輯器為例,如果選用 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(newBorderLayout()); 來改變其布局管理器為一個新的 BorderLayout 對象。

  然後,我們對布局管理器的直接操作就結束了,剩下的隻需要往容器裡添加元件。如果使用 FlowLayout,我們隻需要使用容器的 add(Component c) 方法添加元件就行了。但是,如果使用 BorderLayout 就不一樣了,因為要指定是把元件添加到哪個區域啊。那我們就使用容器的 add(Component c, Object o) 方法添加元件,該方法的第二個參數就是指明添加到的區域用的。例如,上述編輯器中要添加一個多行文本框到 BorderLayout 的中部,就可以用 frame.getContentPane().add(newJTextArea(5, 15), BorderLayout.CENTER) 來實作。

  BorderLayout的五個區域分别是用下列五個常量來描述的:

BorderLayout.EAST 東(右)
BorderLayout.SOUTH 南(下)
BorderLayout.WEST 西(左)
BorderLayout.NORTH 北(上)
BorderLayout.CENTER

  剛才已經提到了使用 JPanel。JPanel 作為一個容器,可以包容一些元件,然後将這個 JPanel 對象作為一個元件添加到另一個容器 (稱作父容器) 中。這個功能有什麼好處呢?

  上面不是提到 BorderLayout的一個區域中隻能添加一個元件嗎?但是我們的編輯器需要添加兩個按鈕到它的北部,怎麼辦?下面的例子中,我們就會用的一個 JPanel 包容了這兩個按鈕,然後再将這個 JPanel 對象作為一個元件添加到設定布局管理器為 BorderLayout 的 Content Pane 中。

  上面說到各布局管理器的布局特點的時候,幾乎每一種都是一個區域隻能添加一個元件,那我們想添加多個元件到一個區域的時候,就要用到 JPanel了。如果還沒有明白,稍後看一段程式可能更易于了解。

  而滾動窗格 (JScrollPane)呢?它是一個能夠自己産生滾動條的容器,通常隻包容一個元件,并且根據這個元件的大小自動産生滾動條。比如上面講 JTextArea 的時候提到:JTextAera 會随使用者輸入的内容自動擴充大小,很容易打破各元件的布局。但是,如果我們将它包容在一個滾動窗格中,它的擴充就不會直接反映在大小的變化上,而會反映在滾動窗格的滾動條上,也就不會打破各元件的布局了。稍後的例子會讓你清清楚楚。

  是不是等着看例子了?好,例子來了:

<span style="font-size:18px;">import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public final class TestPanels extends JFrame {

    public static void main(String[] args) {
        TestPanels tp = new TestPanels();
        tp.setVisible(true);
    }

    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();
    }

}</span>
           

  這個例子的運作結果如下圖,正是我們想要的結果——上面兩個按鈕,下面是一個可以滾動的多行文本框:

  上例中首先産生了一個 JPanel對象 (JPanel panel = new JPanel();),然後将兩個按鈕置于其中 (panel.add ...);然後産生了一個多行文本框 (JTextAreatextArea = 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,是以不用再設定了。

原文:http://blog.csdn.net/jamesfancy/article/details/1196585

參考:http://www.weixueyuan.net/java/rumen_11/