java佈局管理器的使用

深入瞭解佈局管理器
文楓 ([email protected])
深圳全通數碼高級程序員
2002 年 7 月

1 前言

隨着Internet的飛速發展,Java技術也得到了越來越廣泛的應用。而無論我們是採用J2SE、J2EE還是J2ME,GUI都是不能迴避的問題。現在的應用軟件越來越要求界面友好、功能強大而又使用簡單。而衆所周知,在Java中進行GUI設計相對於其跨平臺、多線程等特性的實現要複雜和麻煩許多。這也是很多Java程序員抱怨的事情。但GUI已經成爲程序發展的方向,所以我們也必須瞭解Java的GUI設計方法和特點。其實,採用Java提供的佈局管理器接口和相應的佈局管理類,我們也可以做出相當漂亮的界面來,當然實現起來肯定要比VB麻煩許多。本文試圖通過自己的開發經歷介紹一些具體的應用實例,希望能給那些曾經象我一樣苦悶的Java癡迷者一些幫助。

2 Java中的佈局管理器

2.1 爲什麼要使用佈局

在實際編程中,我們每設計一個窗體,都要往其中添加若干組件。爲了管理好這些組件的佈局,我們就需要使用佈局管理器。比如說,設計一個簡單的計算器,或一個文本編輯器等等。這些組件是讓JVM 自己任意安排呢?還是按照一定的位置關係進行規範的安排呢?當然應該選擇後者。將加入到容器的組件按照一定的順序和規則放置,使之看起來更美觀,這就是佈局。在Java中,佈局由佈局管理器 (LayoutManager) 來管理。那麼,我們在什麼時候應該使用佈局管理器?應選擇哪種佈局管理器?又該怎樣使用佈局管理器呢?

如果你寫的是GUI程序,在使用AWT/Swing組件時就不應硬性設置組件的大小和位置,而應該使用Java的佈局管理器(LayoutManager)來設置和管理可視組件的大小和位置,否則就有可能造成佈局混亂。不信,你可以新建一個Frame(或JFrame),通過setBounds()方法往其中添加幾個Button(或JButton),一旦你將窗體拉大或縮小時,你會發現組件的排列完全不是按你所預想的那樣。爲了解決這個問題,即當窗體(或容器)縮放時,組件位置也隨之合理調整,我們就需要使用佈局管理器。

爲此,我們首先要知道Java的佈局方式,Java提供的API中有些什麼佈局管理器,它們的佈局特點是什麼。

2.2 Java的佈局方式

我們都知道,Java的GUI界面定義是由AWT類包和Swing類包來完成的。它在佈局管理上採用了容器和佈局管理分離的方案。也就是說,容器只管將其他組件放入其中,而不管這些組件是如何放置的。對於佈局的管理交給專門的佈局管理器類(LayoutManager)來完成。

現在我們來看Java中佈局管理器的具體實現。我們前面說過,Java中的容器類(Container),它們只管加入組件(Component),也就是說,它只使用自己的add()方法向自己內部加入組件。同時他記錄這些加入其內部的組件的個數,可以通過container.getComponentCount()方法類獲得組件的數目,通過container.getComponent(i)來獲得相應組件的句柄。然後LayoutManager類就可以通過這些信息來實際佈局其中的組件了。

Java已經爲我們提供了幾個常用的佈局管理器類,例如: FlowLayout、BorderLayout、GridLayout、GridBagLayout等。下面列表說明它們的佈局特點:

包 類 特點
java.awt CardLayout 將組件象卡片一樣放置在容器中,在某一時刻只有一個組件可見
java.awt FlowLayout 將組件按從左到右而後從上到下的順序依次排列,一行不能放完則折到下一行繼續放置
java.awt GridLayout 形似一個無框線的表格,每個單元格中放一個組件
java.awt BorderLayout 將組件按東、南、西、北、中五個區域放置,每個方向最多隻能放置一個組件
java.awt GridBagLayout 非常靈活,可指定組件放置的具體位置及佔用單元格數目
Javax.swing BoxLayout 就像整齊放置的一行或者一列盒子,每個盒子中一個組件
Javax.swing SpringLayout 根據一組約束條件放置子組件
Javax.swing ScrollPaneLayout 專用於JScrollPane,含一個Viewport,一個行頭、一個列頭、兩個滾動條和四個角組件
Javax.swing OverlayLayout 以彼此覆蓋的形式疊置組件
Javax.swing ViewportLayout JViewport的默認佈局管理器

事實上,在大多數情況下,綜合運用好這些佈局管理器已可以滿足需要。當然對於特殊的具體應用,我們可以通過實現LayoutManager或LayoutManager2接口來定義自己的佈局管理器。下面我們通過幾個實例來了解幾個常用的佈局管理器的使用方法。

3 GUI設計應用實例

3.1 FlowLayout/GridLayout/BorderLayout的應用實例

3.1.1應用背景

假設我們要編寫一個簡單的計算器JApplet,其基本界面如下


3.1.2解決方法

通過其界面要求可知,我們可以通過將"BackSpace"和"Clear"JButton放置在一個JPanel(1)中,採用FlowLayout佈局;將顯示結果的JTextField和該JPanel一起放置到另外一個JPanel(2),採用GridLayout佈局;而將其它的JButton則放置在另外一個JPanel(3)中,採用GridLayout佈局;再將JPanel(2)和JPanel(3)加入該JApplet,即可實現界面需求。具體實現方法如下:

/**以FlowLayout佈局JPanel(1)*/
JPanel p1 = new JPanel(new FlowLayout()); //默認組件從居中開始
//加入"BackSpace"和"Clear"JButton
p1.add(backButton);    
p1.add(clearButton);
/**以GridLayout佈局JPanel(2)*/
JPanel p2 = new JPanel(new GridLayout(2, 1));    //放置2行,每行1個組件
//加入顯示結果的JTextField和JPanel(1)
p2.add(displayField);  
p2.add(p1);
/**以GridLayout佈局JPanel(3)*/
JPanel p3 = new JPanel(new GridLayout(4, 5));    //放置4行,每行5個組件
String buttonStr = "789/A456*B123-C0.D+=";
for (int i = 0; i < buttonStr.length(); i++)
this.addButton(p3, buttonStr.substring(i, i + 1));
    
//addButton方法
     private void addButton(Container c, String s)
     {
         JButton b = new JButton(s);
         if (s.equals("A"))
             b.setText("sqrt");
         else if (s.equals("B"))
             b.setText("1/x");
         else if (s.equals("C"))
             b.setText("%");
         else if (s.equals("D"))
             b.setText("+/-");
         b.setForeground(Color.blue);
         c.add(b);
         b.addActionListener(this);
     }
/**以BorderLayout佈局JApplet*/
     this.setLayout(new BorderLayout());
this.add(p2, "North");
this.add(p3, "Center");

這樣,就一切OK啦。具體的實現代碼可參見附件中的CalculateApplet.java文件。

3.2 帶工具欄和狀態欄的GridLayout/BorderLayout應用實例

3.2.1實際問題

在很多情況下我們需要動態設置工具欄和狀態欄,看下面的應用實例:




以上是在視圖的工具欄和狀態欄都被複選的時候,以下分別爲某一個沒選或都未選的情況。




3.2.2解決方法

  1. /**工具欄JToolBar採用從左開始的FlowLayout佈局*/
  2. JToolBar toolBar = new JToolBar();     
  3. toolBar.setBorderPainted(false); //不畫邊界
  4.      toolBar.setLayout(new FlowLayout(FlowLayout.LEFT));
  5.      
  6. /**窗體採用動態的BorderLayout佈局,通過獲取工具欄或狀態欄的複選標記進行界面的動態調整*/
  7. JSplitPane splitPane = new JSplitPane();
  8. splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); //設置統計窗口分隔條的方向
  9. splitPane.setDividerLocation(300);     //設置分隔條的位置
  10. splitPane.setOneTouchExpandable(true);
  11. JCheckBoxMenuItem toolBarItem = new JCheckBoxMenuItem("工具欄(T)", true);
  12.     JLabel statusLabel = new JLabel("當前統計目標:");
  13.     JCheckBoxMenuItem statusBarItem = new JCheckBoxMenuItem("狀態欄(S)", true);
  14.  
  15. /**設置系統窗體佈局並動態設置工具欄和狀態欄*/
  16.     private void setLayout()
  17.      {
  18.         if (toolBarItem.getState() &&am;' statusBarItem.getState())
  19.          {
  20.             this.getContentPane().add(BorderLayout.NORTH, toolBar);
  21.             this.getContentPane().add(BorderLayout.CENTER, splitPane);
  22.             this.getContentPane().add(BorderLayout.SOUTH, statusLabel);
  23.          }
  24.         else if (toolBarItem.getState() && !statusBarItem.getState())
  25.          {
  26.             this.getContentPane().add(BorderLayout.NORTH, toolBar);
  27.             this.getContentPane().remove(statusLabel);
  28.          }
  29.         else if (statusBarItem.getState() && !toolBarItem.getState())
  30.          {
  31.             this.getContentPane().add(BorderLayout.SOUTH, statusLabel);
  32.             this.getContentPane().remove(toolBar);
  33.          }
  34.         else if (!toolBarItem.getState() && !statusBarItem.getState())
  35.          {
  36.             this.getContentPane().remove(toolBar);
  37.             this.getContentPane().remove(statusLabel);
  38.          }
  39.         this.show(); //添加或移去組件後刷新界面
  40.      }
    
通過該方法即可實現界面的動態刷新與調整。

3.3 GridBagLayout應用實例

3.3.1實際問題

GridBagLayout是Java API提供的一個較複雜的佈局管理器,利用好它可以解決許多實際編程中的令人煩惱的界面設計問題。看下面的界面應用實例:



3.3.2解決方法

這個界面的設計比較複雜,涉及多個標籤域(JLabel)、文本域(JTextField、JTextArea),且標籤域的大小還不一樣,如附件標籤;並當窗體縮放時,標籤域的大小應不改變,而文本域則必須自適應縮放。如何來實現呢?請看下面的代碼:(工具欄的實現不再贅述)

  1. /**系統的界面佈局實現*/
  2. GridBagConstraints gridBag = new GridBagConstraints();
  3. gridBag.fill = GridBagConstraints.HORIZONTAL;  //以水平填充方式佈局
  4.      gridBag.weightx = 0;  //行長不變
  5.      gridBag.weighty = 0;  //列高不變
  6.      fromLabel.setForeground(Color.blue);
  7.      fromLabel.setFont(new Font("Alias", Font.BOLD, 16));
  8.     this.add(fromLabel, gridBag, 0, 1, 1, 1);  //指定發信人標籤位置
  9.      receiveLabel.setForeground(Color.blue);
  10.      receiveLabel.setFont(new Font("Alias", Font.BOLD, 16));
  11.     this.add(receiveLabel, gridBag, 0, 2, 1, 1); //指定收信人標籤位置及大小
  12.      ccLabel.setForeground(Color.blue);
  13.      ccLabel.setFont(new Font("Alias", Font.BOLD, 16));
  14.     this.add(ccLabel, gridBag, 0, 3, 1, 1); //指定抄送人標籤位置及大小
  15.      subjectLabel.setForeground(Color.blue);
  16.      subjectLabel.setFont(new Font("Alias", Font.BOLD, 16));
  17.      his.add(subjectLabel, gridBag, 0, 4, 1, 1); //指定主題標籤位置及大小
  18.      accessoryLabel.setForeground(Color.blue);
  19.      accessoryLabel.setFont(new Font("Alias", Font.BOLD, 16));
  20.     this.add(accessoryLabel, gridBag, 0, 5, 1, 1); //指定附件標籤位置及大小
  21.  
  22.      gridBag.weightx = 100; //行自適應縮放
  23.      gridBag.weighty = 0;//列高不變
  24.      fromField.setText("[email protected]");
  25.        this.add(fromField, gridBag, 1, 1, 2, 1); //指定發信人文本域(JTextField)位置及大小
  26.     this.add(receiveField, gridBag, 1, 2, 2, 1); //指定收信人文本域(JTextField)位置及大小
  27.     this.add(ccField, gridBag, 1, 3, 2, 1); //指定抄送人文本域(JTextField)位置及大小
  28.     this.add(subjectField, gridBag, 1, 4, 2, 1); //指定主題文本域(JTextField)位置及大小
  29.      accessoryArea.setEditable(false);  
  30. //設置不顯示水平滾動條(該JTextArea置於JScrollPane中)
  31. accessoryScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
  32.     this.add(accessoryScroll, gridBag, 1, 5, 2, 1); //指定附件文本區(JTextArea)位置及大小
  33.  
  34.      gridBag.fill = GridBagConstraints.BOTH;//採用全填充方式佈局
  35.      gridBag.weightx = 100;//行自適應縮放
  36.      gridBag.weighty = 100;//列自適應縮放
  37.      mailArea.setBackground(Color.blue);
  38.      mailArea.setForeground(Color.yellow);
  39.      mailArea.setTabSize(4);
  40.     //指定信件主體區(JTextArea)的位置及大小。(該JTextArea也置於JScrollPane中)
  41.     this.add(scroll, gridBag, 0, 6, 3, 1);
  42.  
  43.      在上面用到一個方法add(),這個方法是自己定義的:
  44. private void add(Component c, GridBagConstraints gbc, int x, int y, int w, int h)
  45.      {
  46.          gbc.gridx = x;
  47.          gbc.gridy = y;
  48.          gbc.gridheight = h;
  49.          gbc.gridwidth = w;
  50.         this.getContentPane().add(c, gbc);
  51.      }
    
在用到GridBagLayout佈局管理器的組件添加方法中,都可以重用它。事實上,你還可以在方法最前面加一個參數Container cn,而將方法中的this相應的改爲cn,就可以通用於所有需要使用GridBagLayout進行佈局管理的容器中。在下面的複雜例程中我們就會用到。

3.4 綜合多個佈局的複雜應用實例

3.4.1實際問題

請看下面的實際應用界面要求:


(圖3.4-1)



(圖3.4-2)



(圖3.4-3)

在這個具體應用中,底部的JButton組是確定的,但JTabbedPane的每一個選項都不同,如何實現呢?

3.4.2解決方案

首先我們可以採用BorderLayout確定主題對話框的佈局方式,實現方法如下:

  1. JTabbedPane dbTabPane = new JTabbedPane();
  2. …… //下面需要用到的JButton等組件變量定義(或聲明)
  3. private void initLayout()
  4. {
  5. initDBTabPane();//初始化JTabbedPane:DBTabPane組件
  6.     this.getContentPane().add(BorderLayout.CENTER, dbTabPane);
  7.     //將JTabbedPane組件:dbTabPane佈局於JDialog對話框的中間
  8.          initButtonPanel();//初始化JPanel:ButtonPanel組件
  9.         this.getContentPane().add(BorderLayout.SOUTH, buttonPanel);
  10.     //將JPanel組件:buttonPanel佈局於JDialog對話框的底部(南面)
  11.      }
  12.  
  13. private void initDBTabPane()
  14. {
  15.         JPanel loginPanel = new JPanel(new GridLayout(10, 1));
  16. //爲保證兩個JCheckBox組件位於頂端,設置爲共10行,每行一個組件的佈局,但只
  17. //放置界面要求的兩個組件,這樣就保持了界面的美觀,否則如定義爲
  18. //Gridlayout(2,1)則會使兩個組件居中,而且中間會隔開較長的距離。
  19. pwdBox.setMnemonic('P');
  20.          loginPanel.add(pwdBox);
  21.          dspBox.setMnemonic('D');
  22.          loginPanel.add(dspBox);
  23.          dbTabPane.add("Login", loginPanel); //設置"Login"JPanel(圖3.4-1)的佈局
  24.          needRadio.setMnemonic('N');
  25.          allRadio.setMnemonic('A');
  26.          cacheRadio.setMnemonic('U');
  27.          radioPanel.setBorder(new TitledBorder("Load Option"));//加上邊界標題
  28.          radioPanel.add(needRadio);
  29.          radioPanel.add(allRadio);
  30.          radioPanel.add(cacheRadio);
  31. //以上爲加入需要的JRadioButton組件到指定的JPanel: radioPanel
  32.          queryPanel.add(radioPanel);//加入含JRadioButton組的JPanel到queryPanel
  33.          reqBox.setMnemonic('R');
  34.          boxPanel.add(reqBox);
  35.          saveBox.setMnemonic('S');
  36.          boxPanel.add(saveBox);
  37.          autoBox.setMnemonic('t');
  38.          boxPanel.add(autoBox);
  39. //以上爲加入需要的JCheckBox組到指定的JPanel:boxPanel
  40.          queryPanel.add(boxPanel); //加入含JCheckBox組的JPanel到queryPanel
  41.          dbTabPane.add("Query", queryPanel);//設置"Query"JPanel(圖3.4-2)的佈局
  42.          initDrvPanel();
  43.      }
  44.  
  45. /**設置"Drivers"JPanel(圖3.4-3)的佈局*/
  46. private void initDrvPanel()
  47. {
  48.          gridBag.fill = GridBagConstraints.HORIZONTAL;
  49.          gridBag.weightx = 100;
  50.          gridBag.weighty = 0;
  51.          tipLabel.setForeground(Color.black);
  52.         this.add(drvPanel, tipLabel, gridBag, 0, 0, 4, 1);
  53.          urlLabel.setForeground(Color.black);
  54.         this.add(drvPanel, urlLabel, gridBag, 0, 5, 4, 1);
  55.          urlField.setEditable(false);
  56.         this.add(drvPanel, urlField, gridBag, 0, 6, 4, 1);
  57.          gridBag.weightx = 0;
  58.          gridBag.weighty = 0;
  59.          addButton.setMnemonic('A');
  60.         this.add(drvPanel, addButton, gridBag, 3, 1, 1, 1);
  61.          editButton.setMnemonic('E');
  62.         this.add(drvPanel, editButton, gridBag, 3, 2, 1, 1);
  63.          removeButton.setMnemonic('R');
  64.         this.add(drvPanel, removeButton, gridBag, 3, 3, 1, 1);
  65.          gridBag.fill = GridBagConstraints.BOTH;
  66.          gridBag.weightx = 100;
  67.          gridBag.weighty = 100;
  68.            //設置JTable組件:drvTable的從0到7行第0列的值
  69. for (int i = 0; i < 8; i++)
  70.                 drvTable.setValueAt(drvStrs[i],i,0);  
  71. //設置JTable的列頭
  72. drvTable.getColumn(drvTable.getColumnName(0)).setHeaderValue("All Drivers");
  73.          drvTable.setShowGrid(false);//設置不顯示網格線
  74.         this.add(drvPanel, drvScroll, gridBag, 0, 1, 3, 4);
  75.          dbTabPane.add("Drivers", drvPanel);
  76.     }
  77.  
  78. /**初始化底部JButton組的佈局*/
  79. private void initButtonPanel()
  80.      {
  81.         JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
  82. //從右邊開始進行FlowLayout佈局
  83. okButton.setMnemonic('O');
  84.          buttonPanel.add(okButton);
  85.          cancelButton.setMnemonic('C');
  86.          buttonPanel.add(cancelButton);
  87.          helpButton.setMnemonic('H');
  88.          buttonPanel.add(helpButton);
  89.      }
  90.  
  91. /**給指定的容器cn在指定的(x,y)位置放置指定大小(寬度=w,高度=h)的組件c*/
  92. private void add(Container cn, Component c, GridBagConstraints gbc, int x, int y, int w, int h)
  93. {
  94.          gbc.gridx = x;
  95.          gbc.gridy = y;
  96.          gbc.gridwidth = w;
  97.          gbc.gridheight = h;
  98.          cn.add(c, gbc);
  99.      }     

4 結束語

以上是本人在兩年多J2EE應用開發中,總結的關於用Java進行GUI設計的一些經驗,希望能給曾經象我一樣迷惘,但依舊對Java一往情深,至今仍在摸索探求Java GUI設計捷徑的朋友一些啓示。更希望藉此機會拋磚引玉,與更多的朋友進行交流與探討。其實,在Java中所有的佈局管理器都要實現一個接口,即LayoutManager Inerface或者是它的一個子接口LayoutManager2 Interface,後者用於更復雜的佈局管理。如果在實際應用中,覺得Java API提供的這些佈局管理器仍不夠用,你完全可以自己來實現其中某一個接口的方法,從而爲你自己的具體GUI應用設計提供更好的佈局管理。

5 參考資料

jdk1.4 API http://java.sun.com/j2se/1.4/docs/api/
《Java2圖形設計 卷Ⅱ:SWING》 機械工業出版社
6 附件

JavaSrc.zip包含下列java源代碼和Class代碼:


CalculatorApplet.java爲計算器Applet的實現代碼。可以通過點擊Calculator.html運行;或通過appletviewer來運行。
DBOptions.java爲3.4實例的具體實現代碼。你可以通過自己編寫一個JFrame來調用。
7 附錄:關於作者

文楓(網名:西瓜),國防科大計算機學士,深圳全通數碼高級程序員。目前專注於J2EE應用與開發。休閒時間喜歡旅遊、踢球。你可以通過 [email protected] 與我聯繫。 
發佈了17 篇原創文章 · 獲贊 1 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章