| 在实际编程中,我们每设计一个窗体,都要往其中添加若干组件。为了管理好这些组件的布局,我们就需要使用布局管理器。比如说,设计一个简单的计算器,或一个文本编辑器等等。这些组件是让JVM 自己任意安排呢?还是按照一定的位置关系进行规范的安排呢?当然应该选择后者。将加入到容器的组件按照一定的顺序和规则放置,使之看起来更美观,这就是布局。在Java中,布局由布局管理器 (LayoutManager) 来管理。那么,我们在什么时候应该使用布局管理器?应选择哪种布局管理器?又该怎样使用布局管理器呢?
如果你写的是GUI程序,在使用AWT/Swing组件时就不应硬性设置组件的大小和位置,而应该使用Java的布局管理器(LayoutManager)来设置和管理可视组件的大小和位置,否则就有可能造成布局混乱。不信,你可以新建一个Frame(或JFrame),通过setBounds()方法往其中添加几个Button(或JButton),一旦你将窗体拉大或缩小时,你会发现组件的排列完全不是按你所预想的那样。为了解决这个问题,即当窗体(或容器)缩放时,组件位置也随之合理调整,我们就需要使用布局管理器。
为此,我们首先要知道Java的布局方式,Java提供的API中有些什么布局管理器,它们的布局特点是什么。
我们都知道,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.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"JButtonp1.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.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); }
|
|