您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Java圖形界面開發中的高級Swing容器怎么用,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
我們將會了解一些構建在這些布局管理器之上的容器以及其他的一些無需布局管理器的容器。
我們的探討由Box類開始,我們將會發現使用BoxLayout管理器來創建一個單行或單列組件的最好方法。接下來我們會了解JSplitPane容器,他類似于其中只有兩個組件的特殊的Box。JSplitPane提供了一個分隔欄,用戶可以拖動這個分隔欄來調整組件的大小以滿足各自的需求。
然后我們會探討JTabbedPane容器,其工作方工式類似于一個由CardLayout布局管理器管理的容器,所不同的是容器內建的標簽可以使得我們由一個卡片移動到一個卡片。我們也可以使用JTabbedPane創建多個屏幕,屬性頁對話框用于用戶輸入。
最后討論的兩個高級Swing容器是JScrollPane與JViewport。這兩個組件都提供了在有限的屏幕真實狀態之內顯示大組件集合的能力。JScrollPane為顯示區域添加滾動條,從而我們可以在一個小區域內在大組件周圍移動。事實上,JScrollPane使用JViewport來分割本看不見的大組件部分。
下面我們就開始了解第一個容器,Box類。
11.1 Box類
作為JComponent類的子類,Box類是借助于BoxLayout管理器創建單行或單列組件的一個特殊Java Container。Box容器的作用類似于JPanel(或Panel),但是具有一個不同的默認布局管理器,BoxLayout。在Box之外使用BoxLayout有一些麻煩,而Box簡化了BoxLayout的使用。我們只需三步就可以將BoxLayout管理器與容器相關聯:手動創建容器,創建布局管理器,然后將管理器與容器相關聯。當我們創建一個Box的實例時,我們一次就執行了這三個步驟。另外,我們可以使用Box的名為Box.Filler的內聯類來更好的放置容器內的組件。
11.1.1 創建Box
我們有三種方法來創建Box,一個構造函數以及兩個靜態工廠方法:
public Box(int direction) Box horizontalBox = new Box(BoxLayout.X_AXIS); Box verticalBox = new Box(BoxLayout.Y_AXIS); public static Box createHorizontalBox() Box horizontalBox = Box.createHorizontalBox(); public static Box createVerticalBox() Box verticalBox = Box.createVerticalBox();
注意 ,Box類并沒有被設計用來作為JavaBean組件使用。在IDE中這個容器的使用十分笨拙。
不經常使用的構造函數需要布局管理器主坐標的方向。這個方向是通過BoxLayout的兩個常量來指定的:X_AXIS或Y_AXIS,分別用來創建水平或垂直盒子。我們無需手動指定方向,我們可以簡單的通過所提供的工廠方法來創建所需方向的盒子:createHorizontalBox()或createVerticalBox()。
使用JLabel,JTextField與JButton填充一個水平與垂直Box演示了BoxLayout的靈活性,如圖11-1所示。
對于水平容器,標簽與按鈕以其最優的寬度顯示,因為他們的最大尺寸與最優尺寸相同。文本域使用余下的空間。
在垂直容器中,標簽與按鈕的尺寸也是他們的最優尺寸,因為他們的最大尺寸依然與他們的最優尺寸相同。文本的高度填充了標簽與按鈕沒有使用的高度,而其寬度與容器的寬度相同。
用于創建圖11-1所示屏幕的源碼顯示在列表11-1中。
package swingstudy.ch21; import java.awt.BorderLayout; import java.awt.EventQueue; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextField; public class BoxSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame verticalFrame = new JFrame("Vertical"); verticalFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Box verticalBox = Box.createVerticalBox(); verticalBox.add(new JLabel("Top")); verticalBox.add(new JTextField("Middle")); verticalBox.add(new JButton("Bottom")); verticalFrame.add(verticalBox, BorderLayout.CENTER); verticalFrame.setSize(150, 150); verticalFrame.setVisible(true); JFrame horizontalFrame = new JFrame("Horizontal"); horizontalFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Box horizontalBox = Box.createHorizontalBox(); horizontalBox.add(new JLabel("Left")); horizontalBox.add(new JTextField("Middle")); horizontalBox.add(new JButton("Right")); horizontalFrame.add(horizontalBox, BorderLayout.CENTER); horizontalFrame.setSize(150, 150); horizontalFrame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
11.1.2 Box屬性
如表11-1所示,Box只有兩個屬性。盡管布局屬性由其父類Container繼承了setLayout(LayoutManager)方法,但是如果在Box對象上調用,這個類會拋出一個AWTError。一旦BoxLayout管理器在其構造函數中被設置,那么就能再改變,其方向也不能改變。
11.1.3 使用Box.Filer
Box類具有一個內聯類Box.Filler,可以幫助我們創建不可見的組件從而更好的為采用BoxLayout布局管理器的容器內的組件進行位置布局。通過直接操作所創建組件的最小,最大與最優尺寸,我們可以創建可以增長來填充未用的空間或是保持固定尺寸的組件,從而屏幕更為用戶所接受。
注意,由技術上來說,Box.Filler的使用并沒有局限于使用BoxLayout布局管理器的容器。我們可以將其用在其他任何使用Component的地方。只是組件是不可見的。
我們無需直接使用Box.Filler類,Box類的一些靜態方法可以幫助我們創建合適的填充器組件。工廠方法可以使得我們通過類型對組件進行分類,而不是通過最小值,最大值或是最優尺寸進行分類。我們將會在接下來的兩節中了解這些方法。
如果我們對類定義感興趣,Box.Filler的類定義顯示如下。類似于Box類,Box.Filler本來也不是作為JavaBean組件來使用的。
public class Box.Filler extends Component implements Accessible { // Constructors public Filler(Dimension minSize, Dimension prefSize, Dimension maxSize); // Properties public AccessibleContext getAccessibleContext(); public Dimension getMaximumSize(); public Dimension getMinimumSize(); public Dimension getPreferredSize(); // Others protected AccessibleContext accessibleContext; public void changeShape(Dimension minSize, Dimension prefSize, Dimension maxSize); }
11.1.4 創建擴展區域
如果一個組件具有較小的最小尺寸與最優尺寸,而最大尺寸要大于屏幕尺寸,組件可以在一個或是兩個方向上進行擴展以占用容器中組件之間的未用空間。在Box的情況下,或者更確切的說,布局管理器為BoxLayout的容器,擴展出現在布局管理器初始選擇的方向上(BoxLayout.X_AXIS或BoxLayout.Y_AXIS)。對于水平的盒子,擴展影響了組件的寬度。對于垂直的盒子,擴展反映在組件的高度上。
通常為這種擴展組件類型指定的名字為膠水(glue)。glue的兩種類型為獨立于方向的glue與方向相關的glue。下面的Box工廠方法用于創建膠合組件:
public static Component createGlue() // Direction independent Component glue = Box.createGlue(); aBox.add(glue); public static Component createHorizontalGlue(); // Direction dependent: horizontal Component horizontalGlue = Box.createHorizontalGlue(); aBox.add(horizontalGlue); public static Component createVerticalGlue() // Direction dependent: vertical Component verticalGlue = Box.createVerticalGlue(); aBox.add(verticalGlue);
一旦我們創建了glue,我們就可以像添加其他的組件一樣將其添加到容器中,通過Container.add(Component)或是其他的add()方法。glue可以使得我們在容器內對齊組件,如圖11-2所示。
我們可以將膠合組件添加到任何其布局管理器考慮到組件的最小尺寸,最大尺寸與最優尺寸的容器中,例如BoxLayout。例如,圖11-3演示了當我們將一個膠合組件添加到JMenuBar而在最后一個JMenu之前的樣子。因為JMenuBar的布局管理器為BoxLayout(實際上是子類javax.swing.plaf.basic.DefaultMenuLayout),這一操作可以將最后一個菜單推到工具欄的右邊,類似于Motif/CDE風格的幫助菜單。
注意,我們推薦避免使用膠合組件的這種功能來設置菜單欄上的菜單。事實上JMenuBar的public void sethelpMenu(JMenu menu)將會實現這種行為而且不會拋出Error。當然,我們中的許多人仍在等待這種操作。
11.1.5 創建固定區域
因為膠合組件會擴展來填充可用的空間,如果我們希望組件之間有一段固定的距離,我們需要創建一個固定組件,或strut。當我們這樣做時,我們需要指定strut的尺寸。strut可以是二維的,需要我們指定組件的寬度或調試;或者也可以是一維的,需要我們指定寬度或高度。
public static Component createRigidArea(Dimension dimension) // Two-dimensional Component rigidArea = Box. createRigidArea(new Dimension(10, 10)); aBox.add(rigidArea); public static Component createHorizontalStrut(int width) // One-dimensional: horizontal Component horizontalStrut = Box. createHorizontalStrut(10); aBox.add(horizontalStrut); public static Component createVerticalStrut(int height) // One-dimensional: vertical Component verticalStrut = Box. createVerticalStrut(10); aBox.add(verticalStrut);
注意,盡管使用createGule()方法創建的方向無關的膠合組件在我們修改容器方向時并沒有副作用,然而創建固定區域會在修改坐標時引起布局問題。(想像一下拖動菜單欄)這是因為組件具有一個最小尺寸。使用createRigidArea()方法并不推薦,除非我們確實需要一個二維的空組件。
圖11-4顯示了一些固定組件。注意,我們可以變化不同的組件之間的固定距離,而且容器最末的固定組件并沒有效果。在用戶調整屏幕之后,組件之間的固定距離會保持不變,如圖11-4所示。
11.2 JSplitPane類
類似于Box容器,JSplitPane容器允許我們在單行或單列中顯示組件。然而Box可以包含任意數量的組件,JSplitPane只可以用來顯示兩個組件。組件可以變化尺寸并通過一個可移動的分隔欄進行分隔。分隔欄可以使得用戶可以通過拖拽分隔欄來調整所包含組件的尺寸。圖11-5顯示了垂直與水平分割面板,同時顯示在移動分隔欄之前與之后的樣子。
11.2.1 創建JSplitPane
JSplitPane有五個構造函數。通過這些構造函數,我們可以初始化所包含組件對的方向,設置continuousLayout屬性或是為容器初始化組件對。
public JSplitPane() JSplitPane splitPane = new JSplitPane(); public JSplitPane(int newOrientation) JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); public JSplitPane(int newOrientation, boolean newContinuousLayout) JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true); public JSplitPane(int newOrientation, Component newLeftComponent, Component newRightComponent) JComponent topComponent = new JButton("Top Button"); JComponent bottomComponent = new JButton("Bottom Button"); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topComponent, bottomComponent); public JSplitPane(int newOrientation, boolean newContinuousLayout, Component newLeftComponent, Component newRightComponent) JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, topComponent, bottomComponent);
除非特別指定,默認方向為水平方向。方向可以通過JSplitPane的常量VERTICAL_SPLIT或HORIZONTAL_SPLIT來指定。continuousLayout屬性設置瘊定了當用戶拖動分隔欄時分隔面板如何響應。當設置為false(默認)時,在拖動時只有分隔符被重繪。當設置為true時,在用戶拖拽分隔欄時,JSplitPane會調整尺寸并重繪分隔欄每一邊的組件。
注意,如果方向為JSplitPane.VERTICAL_SPLIT,我們可以將上部的組件看作左側組件,而將下部組件看作右側組件。
如果我們使用無參數的構造函數,分隔面板內的初始組件集合由按鈕組成(兩個JButton組件)。其他的兩個構造函數顯示的設置了初始的兩個組件。奇怪的是,其余的兩個構造函數默認情況下并沒有提供容器內的組件。要添加或修改JSplitPane內的組件,請參看稍后的“修改JSplitPane組件”一節。
11.2.2 JSplitPane屬性
表11-2顯示了JSplitPane的17個屬性。
設置方向
除了在構造函數中初始化方向以外,我們可以通過將方向屬性修改為JSplitPane.VERTICAL_SPLIT或是JSplitPane.HORIZONTAL_SPLIT來修改JSplitPane方向。如果我們試著將屬性修改為非等同的設置,則會拋出IllegalArgumentException。
不推薦在運行時動態修改方向,因為這會使用戶感到迷惑。然而,如果我們正在使用可視化開發工具,我們可以在創建JSplitPane之后顯示設置方向屬性。當沒有進行可視化編程時,我們通常在創建JSplitPane時初始化方向。
修改JSplitPane組件
有四個讀寫屬性可以用來處理JSplitPane內組件的不同位置:bottomComponent, leftComponent, rightComponent與topComponent。事實上,這四個屬性表示兩種內部組件:左邊與上部組件是一種;右邊與下部組件表示另一種。
我們應該使用與我們的JSplitPane的方向相適應的屬性。使用不合適的屬性方法會使得程序員的維護生命十分困難。想像一下,在創建用戶界面之后,在六個月之后看到如下的代碼:
JComponent leftButton = new JButton("Left"); JComponent rightButton = new JButton("Right"); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setLeftComponent(leftButton); splitPane.setRightComponent(rightButton);
如果我們看一下代碼,基于變量名以及setXXXComponent()方法的使用,我們也許會認為屏幕在左邊包含一個按鈕,而右邊也是一個按鈕。但是實例化的JSplitPane具有一個垂直方向,所創建的界面如圖11-6所示。所用的變量是按鈕的標簽,而不是他們的位置。
如果setTopComponent()與setBottomComponent()方法使用更好的變量名,代碼會更容易理解:
JComponent topButton = new JButton("Left"); JComponent bottomButton = new JButton("Right"); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setTopComponent(topButton); splitPane.setBottomComponent(bottomButton);
移動JSplitPane分隔符
初始時,分隔符顯示在上部組件的下面或是左邊組件的右邊合適尺寸處。任何時候,我們可以通過調用JSplitPane的restToPreferredSizes()方法來重新設置分隔位置。如果我們要編程來定位分隔符,我們可以通過setDividerLocation(newLocation)來修改dividerLocation屬性。這個屬性可以修改一個int位置,表示距離上部或左邊的絕對距離,或者是設置為一個0.0與1.0之間的double值,表示JSplitPane容器寬度的百分比。
注意,如果將屬性設置為0.0與1.0范圍之外的double值則會拋出IllegalArgumentException。
如果我們要設置分隔符的位置,我們必須等到組件已經被實現。本質上,這就意味著組件必須可見。有多種方法可以實現這一操作,最直接的方法就是向JSplitPane關聯一個HierarchyListener,并且監聽HierarchyEvent何時變為SHOWING_CHANGED類型。下面的代碼片段演示了這一操作,將分隔符位置修改為75%。
HierarchyListener hierarchyListener = new HierarchyListener() { public void hierarchyChanged(HierarchyEvent e) { long flags = e.getChangeFlags(); if ((flags & HierarchyEvent.SHOWING_CHANGED) == HierarchyEvent.SHOWING_CHANGED) { splitPane.setDividerLocation(.75); } } }; splitPane.addHierarchyListener(hierarchyListener);
盡管我們可以使用double值設置dividerLocation屬性,我們只會獲得了一個標識絕對位置的int值。
調整組件尺寸與使用可擴展的分隔符
對于JSplitPane內的組件調整尺寸存在限制。JSplitPane會考慮到每一個所包含組件的最小尺寸。如果拖動分隔符使得一個組件縮小到小于其最小尺寸,則滾動面板不會允許用戶拖動分隔符超過這個最小尺寸。
注意,我們可以編程實現將分隔符放在任意位置,甚至是使得組件小于其最小尺寸。然而這并不是一個好主意,因為組件最小尺寸的存在是有原因的。
如果組件的最小維度對于JSplitPane來說過大,我們需要修改組件的最小尺寸,從而分隔符可以使用組件的空間。對于AWT組件,修改一個標準組件的最小尺寸需要子類派生。對于Swing組件,我們可以簡單的通過一個新的Dimension來調用JComponent的setMinimumSize()方法。然而,最小尺寸的設置要合理。如果我們顯式的縮小其最小尺寸,組件就不會正常的工作。
有一個更好的方法可以使得一個組件比其他組件占用更多的空間:將JSplitPane的onTouchExpandable屬性設置為true。當這個屬性為真時,就會為分隔符添加一個圖標,從而使得用戶可以完全折疊起兩個組件中的一個來為另一個組件指定全部的空間。在圖11-7的盒子中,圖標是一個上下箭頭的組合。
圖11-7顯示了這個圖標顯示的樣子(通過Ocean觀感渲染)并且演示了在選擇分隔符上的向上箭頭來將下部的組件擴展為其全部尺寸時的樣子。再一次點擊分隔符上的圖標會使得組件又回到其先前的位置。點擊分隔符上圖標以外的位置會將分隔符定位到使得折疊的組件位于其最優尺寸處。
注意,并沒有較容易的方法來修改擴展分隔符的圖標或是修改分隔符如何渲染。這兩方面都是通過BasicSplitPaneDivider子類來定義并且在用于特定觀感類型的BasicSplitPaneUI子類的createDefaultDivider()方法中創建的。我們可以簡單修改分隔符周圍的邊框,這是一個自定義邊框。
lastDividerLocation屬性可以使得我們或是系統查詢前一個分隔符位置。當用戶選擇maximizer圖標來取消JSplitPane中的一個組件的最小化時,JSplitPane會使用這個屬性。
小心,要小心其最小尺寸是基于容器尺寸或是其初始尺寸的組件。將這些屬性放置在JSplitPane中也許會要求我們手動設置組件的minimum或是最優尺寸。當用在JSplitPane中時最常引起問題的組件就是JTextArea與JScrollPane。
調整JSplitPane尺寸
如果在JSplitPane中存在其所包含的組件的最優尺寸所不需要的額外空間時,這個空間會依據resizeWeight屬性設置進行分配。這個屬性的初始設置為0.0,意味著右邊或是下邊的組件會獲得額外的空間。將這個設置修改為1.0會將所有的空間指定給左邊或上部的組件。0.5則會在兩個組件之間分隔面板。圖11-8顯示了這些變化的效果。
11.2.3 監聽JSplitPane屬性變化
JSplitPane類定義了下列的常量來幫助監聽邊界屬性的變化:
CONTINUOUS_LAYOUT_PROPERTY
DIVIDER_LOCATION_PROPERTY
DIVIDER_SIZE_PROPERTY
LAST_DIVIDER_LOCATION_PROPERTY
ONE_TOUCH_EXPANDABLE_PROPERTY
ORIENTATION_PROPERTY
RESIZE_WEIGHT_PROPERTY
監聽用戶何時移動分隔符的一個方法就是監聽lastDividerLocation屬性的變化。列表11-2中的示例將一個PropertyChangeListener關聯到JSplitPane,從而顯示當前的分隔符位置,當前的最后位置以及前一個最后位置。分隔符上面與下面的組件是OvalPanel類(在第四章中討論),繪制來填充組件的維度。這個組件有助于演示將continuousLayout屬性設置true的效果狀態。
package swingstudy.ch21; import java.awt.BorderLayout; import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JSplitPane; import swingstudy.ch04.OvalPanel; public class PropertySplit { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Property Split"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // create/configure split pane JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setContinuousLayout(true); splitPane.setOneTouchExpandable(true); // create top component JComponent topComponent = new OvalPanel(); splitPane.setTopComponent(topComponent); // create bottom component JComponent bottomComponent = new OvalPanel(); splitPane.setBottomComponent(bottomComponent); // create PropertyChangeListener PropertyChangeListener propertyChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { JSplitPane sourceSplitPane = (JSplitPane)event.getSource(); String propertyName = event.getPropertyName(); if(propertyName.equals(JSplitPane.LAST_DIVIDER_LOCATION_PROPERTY)){ int current = sourceSplitPane.getDividerLocation(); System.out.println("Current: "+current); Integer last = (Integer)event.getNewValue(); System.out.println("Last: "+last); Integer priorLast = (Integer)event.getOldValue(); System.out.println("Prior last: "+priorLast); } } }; // attach listener splitPane.addPropertyChangeListener(propertyChangeListener); frame.add(splitPane, BorderLayout.CENTER); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
如下面的示例輸出所示,當我們運行前面的程序時,我們會注意到lastDividerLocation屬性的變化來反映分隔符的拖動。當用戶停止拖動分隔符時,最后設置被設置為dividerLocation屬性的前一個設置,而不是用戶開始拖動時的初始設置值。當用戶拖動分隔符時,當前值變為最后一個值然后變為前一個最后值。
Current: 11 Last: -1 Prior last: 0 Current: 12 Last: 11 Prior last: -1 Current: 12 Last: 12 Prior last: 11 Current: 12 Last: 11 Prior last: 12 Current: 15 Last: 12 Prior last: 11 Current: 15 Last: 15 Prior last: 12 Current: 15 Last: 12 Prior last: 15 Current: 112 Last: 15 Prior last: 12 Current: 112 Last: 112 Prior last: 15 Current: 112 Last: 15 Prior last: 112
注意,PropertyChangeListener并不支持JSplitPane類的BOTTOM, DIVIDER, LEFT, RIGHT與TOP常量。相反,他們是為add(Component component, Object constraints)方法所用的內部約束。
11.2.4 自定義JSplitPane類型
每一個可安裝的Swing觀感提供了不同的JSplitPane外觀以及組件的默認UIResource值集合。圖11-9顯示了預安裝的觀感類型集合的JSplitPane容器外觀:Motif,Windows以及Ocean。
表11-3顯示了JSplitPane可用的UIResource相關的屬性集合。對于JSplitPane組件,有25個不同的屬性,包括3個分隔符特定的屬性。
關于Java圖形界面開發中的高級Swing容器怎么用就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。