- 2003年5月發在 CCW-I 社區
- 2006年9月般至 CSDN,略作修改
- 2021年5月使用 Markdown 從新排版,略做補充,轉至 SegmentFault。全部代碼在 VSCode + Java Extensions Pack、JDK11 環境下從新排版並試運行成功。因爲舊版 JDK 界面呈現的 BUG 被修復,從新截圖將不能匹配原文內容,因此仍然使用的原版截圖,但在相關位置進行了說明。
做爲一個 Java 程序員,從論壇上感覺到使用 Java 開發程序的人越來多,心中難免欣慰。可是,一樣是從論壇中,看到多數人提到 Java 就覺得是網絡開發——不是這樣的,Java 也能夠開發應用程序,並且能夠開發出漂亮的圖形用戶界面的應用程序,也就是 Windows/XWindow 應用程序。所以,我寫下這篇文章,但願能帶你進入 Java 圖形用戶界面設計之門。java
下面,讓咱們開始……程序員
說明:全部代碼均在 Windows XP + Eclipse 環境下編寫和測試,示例圖片均在 Windows XP 下捕捉。
1、AWT 和 Swing編程
2、框架、監聽器和事件segmentfault
3、按鈕、切換按鈕、複選按鈕和單選按鈕windows
4、文本輸入框、密碼輸入框網絡
5、窗格、滾動窗格和佈局管理架構
AWT 和 Swing 是 Java 設計 GUI 用戶界面的基礎。與 AWT 的重量級組件不一樣,Swing 中大部分是輕量級組件。正是這個緣由,Swing 幾乎無所不能,不但有各式各樣先進的組件,並且更爲美觀易用。因此一開始使用 AWT 的程序員很快就轉向使用 Swing 了。框架
那爲何 AWT 組件沒有消亡呢?由於 Swing 是架構在 AWT 之上的,沒有 AWT 就沒有 Swing。因此程序員能夠根據本身的習慣選擇使用 AWT 或者是 Swing。可是,最好不要兩者混用——除開顯示風格不一樣不說,還極可能形成層次 (Z-Order) 錯亂,好比下例:編輯器
/** * @(#) AwtSwing.java * @author [James Fan](https://segmentfault.com/blog/jamesfancy) */ package jamesfan.uiDemo; 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」的子窗口,咱們會發現一個很是有趣的現象,如圖:函數
注意:不知道從哪一個版本的 JDK 開始,這個 UI 渲染的問題已經不存在了!
顯然 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 Fan](https://segmentfault.com/blog/jamesfancy) */ package jamesfan.uiDemo; 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.setVisible(true); } }
這個例子中,咱們設計了一個窗口類 (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("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
方法,卻不能直接用於添加組件,不然會拋出異常 —— 不信就試試。形成這個問題的緣由只有一個解釋:JFrame
不是一個容器,它只是一個框架。那麼,應該怎麼添加組件呢?
JFrame
有一個 Content Pane,窗口是顯示的全部組件都是添加在這個 Content Pane 中。JFrame
提供了兩個方法:getContentPane
和 setContentPane
就是用於獲取和設置其 Content Pane 的。一般咱們不須要從新設置 JFrame
的 Content Pane,只須要直接獲取默認的 Content Pane 來添加組件等。如:
(new JFrame()).getContentPane().add(new Button("test button"))
按鈕……就是按鈕,不會連按鈕都不知道吧?
發現什麼了嗎?——對了,這一部分是在講各類各樣的按鈕,並且後三種按鈕都有兩種狀態。先看看這些按鈕都長成什麼樣:
上圖中,從上到下,依次就是按鈕、切換按鈕、複選按鈕和單選按鈕。圖示的窗口,就是下面這個例子的運行結果:
/** * @(#) TestButtons.java * @author [James Fan](https://segmentfault.com/blog/jamesfancy) */ package jamesfan.uiDemo; 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); } }
除通常按鈕外,其他三種按鈕都有兩種狀態,即選擇 (按下) 狀態和未選擇 (彈起) 狀態。那麼咱們又該如何判斷呢?切換按鈕 (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
方法以添加動做監聽器。
2021-05-11 補充
Java 8 引入了 Lambda 語法,添加 ActionListener 能夠用更簡潔的語法,好比
jButton.addActionListener( e -> ((JRadioButton) e.getSource()) .setText("add action listener by Lambda since java 8") );
複選框就要特殊一些。雖然它也有 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
對象添加了同一個動做監聽器實例:
/** * @(#) TestActionForDifferenctButtons.java * @author [James Fan](https://segmentfault.com/blog/jamesfancy) */ package jamesfan.uiDemo; import javax.swing.*; import java.awt.event.*; public class TestActionForDifferenctButtons { JButton b; JRadioButton rb; public TestActionForDifferenctButtons() { 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.setVisible(true); } public static void main(String[] args) { new TestActionForDifferenctButtons(); } }
運行程序後,分別單擊兩個按鈕,相應的,在控制檯能分別獲得以下輸出:
You clicked the JButton You clicked the RadioButton
這說明多個不用的組件添加同一個監聽器是可行的——不過前提是這些組件都能添加這個監聽器。
文本輸入框包括兩種,單行文本輸入框 (JTextField
) 和多行文本輸入框 (JTextArea
)。密碼輸入框則只有一種 (JPasswordField
)。JPasswordField
是 JTextField
的子類,它們的主要區別是 JPasswordField
不會顯示出用戶輸入的東西,而只會顯示出程序員設定的一個固定字符,好比 '*' 或者 '#'。
下面的示例圖和代碼是 JTextField
、JPasswordField
和 JTextArea
的示例:
/** * @(#) TestTexts.java * @author [James Fan](https://segmentfault.com/blog/jamesfancy) */ package jamesfan.uiDemo; 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); } }
上例中,咱們構造了一個寬度爲 15
個字符的單行文本框 (textField = new JTextField(15)
),並使用 addCaretListener
方法添加了一個 CaretListener
(textField.addCaretListener ...
)。CaretListener
監聽文本光標的移動事件。當用戶使用鍵盤、鼠標等移動了文本光標在 JTextField
中的位置時觸發這個事件。咱們須要重載 caretUpdate(CaretEvent e)
對事件進行處理 (public void caretUpdate(CaretEvent e) ...
)。這樣,咱們能夠在這裏作相似 VB 中 TextBox 的 OnChange 事件中作的事情。
JTextField
有 5 個構造方法,經常使用其中的 4 個:
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
有 6 個構造方法,經常使用的也是 4 個:
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
最經常使用,本文主要也就只談談這兩種佈局管理器。下面列表說明它們的佈局特色:
佈局管理器 佈局特色
Layout 類 | 簡要說明 |
---|---|
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(new BorderLayout())
來改變其佈局管理器爲一個新的 BorderLayout
對象。
而後,咱們對佈局管理器的直接操做就結束了,剩下的只須要往容器裏添加組件。若是使用 FlowLayout
,咱們只須要使用容器的 add(Component c)
方法添加組件就好了。可是,若是使用 BorderLayout
就不同了,由於要指定是把組件添加到哪一個區域啊。那咱們就使用容器的 add(Component c, Object o)
方法添加組件,該方法的第二個參數就是指明添加到的區域用的。例如,上述編輯器中要添加一個多行文本框到 BorderLayout
的中部,就能夠用 frame.getContentPane().add(new JTextArea(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 會隨用戶輸入的內容自動擴展大小,很容易打破各組件的佈局。可是,若是咱們將它包容在一個滾動窗格中,它的擴展就不會直接反映在大小的變化上,而會反映在滾動窗格的滾動條上,也就不會打破各組件的佈局了。稍後的例子會讓你清清楚楚。
是否是等着看例子了?好,例子來了:
/** * @(#) TestPanels.java * @author [James Fan](https://segmentfault.com/blog/jamesfancy) */ package jamesfan.uiDemo; 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(); } }
這個例子的運行結果以下圖,正是咱們想要的結果——上面兩個按鈕,下面是一個能夠滾動的多行文本框:
上例中首先產生了一個 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 圖形用戶界面設計》就告一段落了。因爲篇幅有限,這裏說的都是初級知識,有此基礎,設計複雜一點的圖形用戶界面也就不是難事了!