 |
2002 年 7 月 05 日
本文試圖通過作者自己的開發經歷介紹一些具體的應用實例,希望能給那些曾經象作者一樣苦悶的Java癡迷者一些幫助。
前言
隨著Internet的飛速發展,Java技術也得到了越來越廣泛的應用。而無論我們是采用J2SE、J2EE還是J2ME,GUI都是不能回避的問題。現在的應用軟件越來越要求界面友好、功能強大而又使用簡單。而眾所周知,在Java中進行GUI設計相對于其跨平臺、多線程等特性的實現要復雜和麻煩許多。這也是很多Java程序員抱怨的事情。但GUI已經成為程序發展的方向,所以我們也必須了解Java的GUI設計方法和特點。其實,采用Java提供的布局管理器接口和相應的布局管理類,我們也可以做出相當漂亮的界面來,當然實現起來肯定要比VB麻煩許多。本文試圖通過自己的開發經歷介紹一些具體的應用實例,希望能給那些曾經象我一樣苦悶的Java癡迷者一些幫助。
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接口來定義自己的布局管理器。下面我們通過幾個實例來了解幾個常用的布局管理器的使用方法。
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解決方法
/**工具欄JToolBar采用從左開始的FlowLayout布局*/
JToolBar toolBar = new JToolBar();
toolBar.setBorderPainted(false); //不畫邊界
toolBar.setLayout(new FlowLayout(FlowLayout.LEFT));
/**窗體采用動態的BorderLayout布局,通過獲取工具欄或狀態欄的復選標記進行界面的動態調整*/
JSplitPane splitPane = new JSplitPane();
splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); //設置統計窗口分隔條的方向
splitPane.setDividerLocation(300); //設置分隔條的位置
splitPane.setOneTouchExpandable(true);
JCheckBoxMenuItem toolBarItem = new JCheckBoxMenuItem("工具欄(T)", true);
JLabel statusLabel = new JLabel("當前統計目標:");
JCheckBoxMenuItem statusBarItem = new JCheckBoxMenuItem("狀態欄(S)", true);
/**設置系統窗體布局并動態設置工具欄和狀態欄*/
private void setLayout()
{
if (toolBarItem.getState() &&' statusBarItem.getState())
{
this.getContentPane().add(BorderLayout.NORTH, toolBar);
this.getContentPane().add(BorderLayout.CENTER, splitPane);
this.getContentPane().add(BorderLayout.SOUTH, statusLabel);
}
else if (toolBarItem.getState() && !statusBarItem.getState())
{
this.getContentPane().add(BorderLayout.NORTH, toolBar);
this.getContentPane().remove(statusLabel);
}
else if (statusBarItem.getState() && !toolBarItem.getState())
{
this.getContentPane().add(BorderLayout.SOUTH, statusLabel);
this.getContentPane().remove(toolBar);
}
else if (!toolBarItem.getState() && !statusBarItem.getState())
{
this.getContentPane().remove(toolBar);
this.getContentPane().remove(statusLabel);
}
this.show(); //添加或移去組件后刷新界面
}
|
通過該方法即可實現界面的動態刷新與調整。
3.3 GridBagLayout應用實例
3.3.1實際問題
GridBagLayout是Java API提供的一個較復雜的布局管理器,利用好它可以解決許多實際編程中的令人煩惱的界面設計問題。看下面的界面應用實例:
3.3.2解決方法
這個界面的設計比較復雜,涉及多個標簽域(JLabel)、文本域(JTextField、JTextArea),且標簽域的大小還不一樣,如附件標簽;并當窗體縮放時,標簽域的大小應不改變,而文本域則必須自適應縮放。如何來實現呢?請看下面的代碼:(工具欄的實現不再贅述)
/**系統的界面布局實現*/
GridBagConstraints gridBag = new GridBagConstraints();
gridBag.fill = GridBagConstraints.HORIZONTAL; //以水平填充方式布局
gridBag.weightx = 0; //行長不變
gridBag.weighty = 0; //列高不變
fromLabel.setForeground(Color.blue);
fromLabel.setFont(new Font("Alias", Font.BOLD, 16));
this.add(fromLabel, gridBag, 0, 1, 1, 1); //指定發信人標簽位置
receiveLabel.setForeground(Color.blue);
receiveLabel.setFont(new Font("Alias", Font.BOLD, 16));
this.add(receiveLabel, gridBag, 0, 2, 1, 1); //指定收信人標簽位置及大小
ccLabel.setForeground(Color.blue);
ccLabel.setFont(new Font("Alias", Font.BOLD, 16));
this.add(ccLabel, gridBag, 0, 3, 1, 1); //指定抄送人標簽位置及大小
subjectLabel.setForeground(Color.blue);
subjectLabel.setFont(new Font("Alias", Font.BOLD, 16));
his.add(subjectLabel, gridBag, 0, 4, 1, 1); //指定主題標簽位置及大小
accessoryLabel.setForeground(Color.blue);
accessoryLabel.setFont(new Font("Alias", Font.BOLD, 16));
this.add(accessoryLabel, gridBag, 0, 5, 1, 1); //指定附件標簽位置及大小
gridBag.weightx = 100; //行自適應縮放
gridBag.weighty = 0;//列高不變
fromField.setText("admin@watermelon.com");
this.add(fromField, gridBag, 1, 1, 2, 1); //指定發信人文本域(JTextField)位置及大小
this.add(receiveField, gridBag, 1, 2, 2, 1); //指定收信人文本域(JTextField)位置及大小
this.add(ccField, gridBag, 1, 3, 2, 1); //指定抄送人文本域(JTextField)位置及大小
this.add(subjectField, gridBag, 1, 4, 2, 1); //指定主題文本域(JTextField)位置及大小
accessoryArea.setEditable(false);
//設置不顯示水平滾動條(該JTextArea置于JScrollPane中)
accessoryScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
this.add(accessoryScroll, gridBag, 1, 5, 2, 1); //指定附件文本區(JTextArea)位置及大小
gridBag.fill = GridBagConstraints.BOTH;//采用全填充方式布局
gridBag.weightx = 100;//行自適應縮放
gridBag.weighty = 100;//列自適應縮放
mailArea.setBackground(Color.blue);
mailArea.setForeground(Color.yellow);
mailArea.setTabSize(4);
//指定信件主體區(JTextArea)的位置及大小。(該JTextArea也置于JScrollPane中)
this.add(scroll, gridBag, 0, 6, 3, 1);
在上面用到一個方法add(),這個方法是自己定義的:
private void add(Component c, GridBagConstraints gbc, int x, int y, int w, int h)
{
gbc.gridx = x;
gbc.gridy = y;
gbc.gridheight = h;
gbc.gridwidth = w;
this.getContentPane().add(c, gbc);
}
|
在用到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確定主題對話框的布局方式,實現方法如下:
JTabbedPane dbTabPane = new JTabbedPane();
…… //下面需要用到的JButton等組件變量定義(或聲明)
private void initLayout()
{
initDBTabPane();//初始化JTabbedPane:DBTabPane組件
this.getContentPane().add(BorderLayout.CENTER, dbTabPane);
//將JTabbedPane組件:dbTabPane布局于JDialog對話框的中間
initButtonPanel();//初始化JPanel:ButtonPanel組件
this.getContentPane().add(BorderLayout.SOUTH, buttonPanel);
//將JPanel組件:buttonPanel布局于JDialog對話框的底部(南面)
}
private void initDBTabPane()
{
JPanel loginPanel = new JPanel(new GridLayout(10, 1));
//為保證兩個JCheckBox組件位于頂端,設置為共10行,每行一個組件的布局,但只
//放置界面要求的兩個組件,這樣就保持了界面的美觀,否則如定義為
//Gridlayout(2,1)則會使兩個組件居中,而且中間會隔開較長的距離。
pwdBox.setMnemonic('P');
loginPanel.add(pwdBox);
dspBox.setMnemonic('D');
loginPanel.add(dspBox);
dbTabPane.add("Login", loginPanel); //設置"Login"JPanel(圖3.4-1)的布局
needRadio.setMnemonic('N');
allRadio.setMnemonic('A');
cacheRadio.setMnemonic('U');
radioPanel.setBorder(new TitledBorder("Load Option"));//加上邊界標題
radioPanel.add(needRadio);
radioPanel.add(allRadio);
radioPanel.add(cacheRadio);
//以上為加入需要的JRadioButton組件到指定的JPanel: radioPanel
queryPanel.add(radioPanel);//加入含JRadioButton組的JPanel到queryPanel
reqBox.setMnemonic('R');
boxPanel.add(reqBox);
saveBox.setMnemonic('S');
boxPanel.add(saveBox);
autoBox.setMnemonic('t');
boxPanel.add(autoBox);
//以上為加入需要的JCheckBox組到指定的JPanel:boxPanel
queryPanel.add(boxPanel); //加入含JCheckBox組的JPanel到queryPanel
dbTabPane.add("Query", queryPanel);//設置"Query"JPanel(圖3.4-2)的布局
initDrvPanel();
}
/**設置"Drivers"JPanel(圖3.4-3)的布局*/
private void initDrvPanel()
{
gridBag.fill = GridBagConstraints.HORIZONTAL;
gridBag.weightx = 100;
gridBag.weighty = 0;
tipLabel.setForeground(Color.black);
this.add(drvPanel, tipLabel, gridBag, 0, 0, 4, 1);
urlLabel.setForeground(Color.black);
this.add(drvPanel, urlLabel, gridBag, 0, 5, 4, 1);
urlField.setEditable(false);
this.add(drvPanel, urlField, gridBag, 0, 6, 4, 1);
gridBag.weightx = 0;
gridBag.weighty = 0;
addButton.setMnemonic('A');
this.add(drvPanel, addButton, gridBag, 3, 1, 1, 1);
editButton.setMnemonic('E');
this.add(drvPanel, editButton, gridBag, 3, 2, 1, 1);
removeButton.setMnemonic('R');
this.add(drvPanel, removeButton, gridBag, 3, 3, 1, 1);
gridBag.fill = GridBagConstraints.BOTH;
gridBag.weightx = 100;
gridBag.weighty = 100;
//設置JTable組件:drvTable的從0到7行第0列的值
for (int i = 0; i < 8; i++)
drvTable.setValueAt(drvStrs[i],i,0);
//設置JTable的列頭
drvTable.getColumn(drvTable.getColumnName(0)).setHeaderValue("All Drivers");
drvTable.setShowGrid(false);//設置不顯示網格線
this.add(drvPanel, drvScroll, gridBag, 0, 1, 3, 4);
dbTabPane.add("Drivers", drvPanel);
}
/**初始化底部JButton組的布局*/
private void initButtonPanel()
{
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
//從右邊開始進行FlowLayout布局
okButton.setMnemonic('O');
buttonPanel.add(okButton);
cancelButton.setMnemonic('C');
buttonPanel.add(cancelButton);
helpButton.setMnemonic('H');
buttonPanel.add(helpButton);
}
/**給指定的容器cn在指定的(x,y)位置放置指定大小(寬度=w,高度=h)的組件c*/
private void add(Container cn, Component c, GridBagConstraints gbc, int x, int y, int w, int h)
{
gbc.gridx = x;
gbc.gridy = y;
gbc.gridwidth = w;
gbc.gridheight = h;
cn.add(c, gbc);
}
|
 |
結束語
以上是本人在兩年多J2EE應用開發中,總結的關于用Java進行GUI設計的一些經驗,希望能給曾經象我一樣迷惘,但依舊對Java一往情深,至今仍在摸索探求Java GUI設計捷徑的朋友一些啟示。更希望借此機會拋磚引玉,與更多的朋友進行交流與探討。其實,在Java中所有的布局管理器都要實現一個接口,即LayoutManager Inerface或者是它的一個子接口LayoutManager2 Interface,后者用于更復雜的布局管理。如果在實際應用中,覺得Java API提供的這些布局管理器仍不夠用,你完全可以自己來實現其中某一個接口的方法,從而為你自己的具體GUI應用設計提供更好的布局管理。 |