GUI图形界面(2)

来源:互联网 发布:歼20就是个笑话 知乎 编辑:程序博客网 时间:2024/05/21 17:11

前言

随着 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.awtCardLayout将组件象卡片一样放置在容器中,在某一时刻只有一个组件可见java.awtFlowLayout将组件按从左到右而后从上到下的顺序依次排列,一行不能放完则折到下一行继续放置java.awtGridLayout形似一个无框线的表格,每个单元格中放一个组件java.awtBorderLayout将组件按东、南、西、北、中五个区域放置,每个方向最多只能放置一个组件java.awtGridBagLayout非常灵活,可指定组件放置的具体位置及占用单元格数目Javax.swingBoxLayout就像整齐放置的一行或者一列盒子,每个盒子中一个组件Javax.swingSpringLayout根据一组约束条件放置子组件Javax.swingScrollPaneLayout专用于 JScrollPane,含一个 Viewport,一个行头、一个列头、两个滚动条和四个角组件Javax.swingOverlayLayout以彼此覆盖的形式叠置组件Javax.swingViewportLayoutJViewport 的默认布局管理器

事实上,在大多数情况下,综合运用好这些布局管理器已可以满足需要。当然对于特殊的具体应用,我们可以通过实现 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-1 

(图 3.4-2)
图 3.4-2 

(图 3.4-3)
图 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 应用设计提供更好的布局管理。

原创粉丝点击