乐趣区

关于java:简述-Java-图形用户界面设计-Swing

  • 2003 年 5 月发在 CCW-I 社区
  • 2006 年 9 月般至 CSDN,略做批改
  • 2021 年 5 月应用 Markdown 从新排版,略作补充,转至 SegmentFault。所有代码在 VSCode + Java Extensions Pack、JDK11 环境下从新排版并试运行胜利。因为旧版 JDK 界面出现的 BUG 被修复,从新截图将不能匹配原文内容,所以依然应用的原版截图,但在相干地位进行了阐明。

作为一个 Java 程序员,从论坛上感触到应用 Java 开发程序的人越来多,心中未免快慰。然而,同样是从论坛中,看到少数人提到 Java 就认为是网络开发——不是这样的,Java 也能够开发应用程序,而且能够开发出丑陋的图形用户界面的应用程序,也就是 Windows/XWindow 应用程序。因而,我写下这篇文章,心愿能带你进入 Java 图形用户界面设计之门。

上面,让咱们开始……

阐明:所有代码均在 Windows XP + Eclipse 环境下编写和测试,示例图片均在 Windows XP 下捕获。

目录

一、AWT 和 Swing

二、框架、监听器和事件

三、按钮、切换按钮、复选按钮和单选按钮

四、文本输入框、明码输入框

五、窗格、滚动窗格和布局治理


一、AWT 和 Swing

AWT 和 Swing 是 Java 设计 GUI 用户界面的根底。与 AWT 的重量级组件不同,Swing 中大部分是轻量级组件。正是这个起因,Swing 简直无所不能,岂但有各式各样先进的组件,而且更为好看易用。所以一开始应用 AWT 的程序员很快就转向应用 Swing 了。

那为什么 AWT 组件没有沦亡呢?因为 Swing 是架构在 AWT 之上的,没有 AWT 就没有 Swing。所以程序员能够依据本人的习惯抉择应用 AWT 或者是 Swing。然而,最好不要二者混用——除开显示格调不同不说,还很可能造成档次 (Z-Order) 错乱,比方下例:

/**
 * @(#) AwtSwing.java
 * @author [James Fan](https://segmentfault.com/blog/jamesfancy)
 */

package jamesfan.uiDemo;

import java.awt.BorderLayout;
import java.awt.Button;

import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;

public final class AwtSwing {public static void main(String[] args) {AwtSwing as = new AwtSwing();
        as.show();}

    JFrame frame = new JFrame("Test AWT and SWING");
    JDesktopPane jdp = new JDesktopPane();
    JInternalFrame jif1 = new JInternalFrame("controls");
    JInternalFrame jif2 = new JInternalFrame("cover");

    public AwtSwing() {frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(jdp);

        jif1.setContentPane(new JPanel());
        jif2.setContentPane(new JPanel());

        jif1.getContentPane().setLayout(new BorderLayout());
        jif1.getContentPane().add(new Button("AWT Button"), BorderLayout.WEST);
        jif1.getContentPane().add(new JButton("Swing Button"),
                BorderLayout.EAST);

        jif1.setSize(200, 100);
        jif2.setSize(200, 100);

        jdp.add(jif1);
        jdp.add(jif2);

        frame.setSize(240, 140);
    }

    public void show() {frame.setVisible(true);
        jif1.setVisible(true);
        jif2.setVisible(true);
    }
}

运行这个程序,并用鼠标拖动那个名为“cover”的子窗口,咱们会发现一个十分乏味的景象,如图:

留神:不晓得从哪个版本的 JDK 开始,这个 UI 渲染的问题曾经不存在了!

显然 cover 子窗口是在 controls 子窗口之上的,然而它只罩盖住了 Swing Button,没有罩盖住 AWT Button。再看一会儿,你是不是有这样一种感觉:Swing Button 是“画”下来的,而 AWT Button 则是“贴”下来的。这就是二者混用造成档次错乱的一个例子。

Swing 组件有好看、易用、组件量大等特点,也有毛病——应用 Swing 组件的程序通常会比应用 AWT 组件的程序运行更慢。然而大家都还是更喜爱用 Swing 组件,起因何在?因为随着计算机硬件的降级,一点点速度曾经不是问题。相同的,用户更须要好看的用户界面,开发人员则更须要易用的开发组件。

二、框架、监听器和事件

框架 (Frame, JFrame) 是 Java 图形用户界面的根底,它就是咱们通常所说的窗口,是 Windows/XWindow 应用程序的典型特色。说到 Windows/XWindow,大家很容易联想到“事件 (Event) 驱动”。Java 的图形用户界面正是事件驱动的,并且由各种各样的监听器 (Listener) 负责捕获各种事件。

如果咱们须要对某一个组件的某种事件进行捕获和解决时,就须要为其增加监听器。比方,咱们要在一个窗口 (JFrame) 激活时扭转它的题目,咱们就须要为这个窗口 (JFrame 对象 ) 增加一个能够监听到“激活窗口”这一事件的监听器——WindowListener

怎么增加监听器呢?这通常由组件类提供的一个 addXxxxxListener 的办法来实现。比方 JFrame 就提供有 addWindowListener 办法增加窗口监听器 (WindowListener)。

一个监听器经常不只监听一个事件,而是能够监听相干的多个事件。比方 WindowListener 除了监听窗口激活事件 (windowActivate) 之外,还能够监听窗口敞开事件 (windowClosing) 等。那么这些事件怎么辨别呢?就靠重载监听器类 (Class) 的多个办法 (Method) 了。监听器监听到某个事件后,会主动调用相干的办法。因而咱们只有重载这个办法,就能够解决相应的事件了。

无妨先看一个例子:

/**
 * @(#) TestFrame.java
 * @author [James Fan](https://segmentfault.com/blog/jamesfancy)
 */

package jamesfan.uiDemo;

import javax.swing.*;
import java.awt.event.*;

public class TestFrame extends JFrame {

    private int counter = 0;

    public TestFrame() {
        // 应用匿名类增加一个窗口监听器
        addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {
                System.out.println("Exit when Closed event");
                // 退出应用程序
                System.exit(0);
            }

            public void windowActivated(WindowEvent e) {
                // 扭转窗口题目
                setTitle("Test Frame" + counter++);
            }
        });

        // 设置窗口为固定大小
        setResizable(false);
        setSize(200, 150);
    }

    public static void main(String[] args) {TestFrame tf = new TestFrame();
        tf.setVisible(true);
    }

}

这个例子中,咱们设计了一个窗口类 (public class TestFrame extends JFrame {...}),并且为这个窗口增加了一个窗口监听器 (addWindowListener(new WindowAdapter() ...)。而咱们增加的这个窗口监听器次要监听了两个事件:窗口敞开 (public void windowClosing(WindowEvent e) ...) 和窗口激活 (public void windowActivated(WindowEvent e) ...)。在窗口敞开事件中咱们退出了整个应用程序 (System.exit(0);),而在窗口激活事件中,咱们扭转了窗口的题目 (setTitle("Test Frame" + counter++);)。最初,咱们在 main 办法中显示了这窗口类的一个实例,运行失去下图所示的后果:

这个程序的运行后果就是一个什么货色都没有加的框架,也就是一个空窗口。那么,你晓得显示一个窗口最次要的几句代码吗?不晓得没关系,我来通知你,显示一个窗口只须要做三件事:生成实例 (对象)→设置大小→显示,相应的,就是上面的三句代码:

JFrame frame = new JFrame("Frame's Title");
frame.setSize(400, 300);
frame.setVisible(true);

兴许你会说:第一句的意思我分明,第三句的意思我也明确,为什么肯定要第二句呢?其实想想也就明确了,叫你画一个没法有大小的矩形你能画进去吗?不能。同样,没有大小的窗口,怎么显示?所以咱们须要用 setSize(int width, int height) 办法为其设置大小。咱们还有另一种办法:用 JFramepack() 办法让它本人适配一个大小。pack() 在少数时候是令人满意的,但有时,它也会让你啼笑皆非——多试试就晓得了。

JFrame 中,咱们应用 addWindowListener 办法退出一个监听器 WindowListener (addWindowListener(new WindowAdapter() ...) 去监听产生在 JFrame 上的窗口事件。WindowListener 是一个接口,在 java.awt.event 这个包中,然而上例中好象并没有应用 WindowListener,而是应用的 WindowsAdapter 吧,这是怎么回事?

WindowAdapterWindowsListener 接口的一个最简略的实现,也在 java.awt.event 包中。如果咱们间接应用 WindowListener 产生一个类,须要实现它的每一个办法 (一共 7 个)。但 WindowAdapter 作为 WindowListener 最简略的实现,曾经实现了它的每一个办法为空办法 (即只蕴含空语句,或者说没有语句的办法)。用 WindowAdapter 就只须要重载可能用到的办法 (上例中只有 2 个) 就行了,而不须要再去实现每一个办法。长处不言而喻——缩小编码量。

JFrame 上产生的窗口事件 (WindowEvent) 包含:

事件 阐明
windowActivated(WindowEvent e) 窗口失去焦点时触发
windowClosed(WindowEvent e) 窗口敞开之后触发
windowClosing(WindowEvent e) 窗口敞开时触发
windowDeactivated(WindowEvent e) 窗口失去焦点时触发
windowDeiconified(WindowEvent e)
windowIconified(WindowEvent e)
windowOpened(WindowEvent e) 窗口关上之后触发

上例重载了其中两个办法。如果在上例运行产生的窗口和另外一个应用程序窗口之间来回切换 (在 Windows 操作系统中你能够应用 Alt+Tab 进行切换 )……试试看,你发现了什么?有没有现咱们的示例窗口题目上的数字始终在减少,这便是在 windowActivated 事件中 setTitle("Test Frame" + counter++) 的功绩。

而另一个事件处理函数 windowClosing 中的 System.exit(0) 则保障了当窗口被敞开时退出以后的 Java 应用程序。如果不作这样的解决会怎么呢?试验之后你会发现,窗口尽管敞开了,但程序并没有完结,但此时,除了应用 Ctrl+C 强行完结之外,恐怕也没有其它方法了。所以,这一点十分重要:你想在敞开窗口的时候退出应用程序,那就须要解决 windowClosing 事件。……也不尽然,其实还有另外一个更简略的方法,让 JFrame 本人解决这件事——你只须要如下调用 JFramesetDefaultCloseOperation 即可:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)

在产生 JFrame 对象之后执行上述语句,就能够不必解决 windowsClosing 事件来退出程序了。

咱们能够在 JFrame 对象中增加 AWT 或者 Swing 组件。然而,尽管它有 add 办法,却不能间接用于增加组件,否则会抛出异样 —— 不信就试试。造成这个问题的起因只有一个解释:JFrame 不是一个容器,它只是一个框架。那么,应该怎么增加组件呢?

JFrame 有一个 Content Pane,窗口是显示的所有组件都是增加在这个 Content Pane 中。JFrame 提供了两个办法:getContentPanesetContentPane 就是用于获取和设置其 Content Pane 的。通常咱们不须要从新设置 JFrame 的 Content Pane,只须要间接获取默认的 Content Pane 来增加组件等。如:

(new JFrame()).getContentPane().add(new Button("test button"))

三、按钮、切换按钮、复选按钮和单选按钮

按钮……就是按钮,不会连按钮都不晓得吧?

  • 切换按钮,有两种状态的按钮,即按下状态和弹起状态,若称为抉择状态或未抉择状态。
  • 复选按钮,又叫复选框,用一个小方框中是否打勾来示意两种状态。
  • 单选按钮,又叫收音机按钮,以小圆框打点示意被选中。常成组呈现,一组单选按钮中只有一个能被选中。

发现什么了吗?——对了,这一部分是在讲各种各样的按钮,而且后三种按钮都有两种状态。先看看这些按钮都长成什么样:

上图中,从上到下,顺次就是按钮、切换按钮、复选按钮和单选按钮。图示的窗口,就是上面这个例子的运行后果:

/**
 * @(#) TestButtons.java
 * @author [James Fan](https://segmentfault.com/blog/jamesfancy)
 */

package jamesfan.uiDemo;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.JToggleButton;

public final class TestButtons {public static void main(String[] args) {TestButtons tb = new TestButtons();
        tb.show();}

    JFrame frame = new JFrame("Test Buttons");
    JButton jButton = new JButton("JButton"); // 按钮
    JToggleButton toggle = new JToggleButton("Toggle Button"); // 切换按钮
    JCheckBox checkBox = new JCheckBox("Check Box"); // 复选按钮
    JRadioButton radio1 = new JRadioButton("Radio Button 1"); // 单选按钮
    JRadioButton radio2 = new JRadioButton("Radio Button 2");
    JRadioButton radio3 = new JRadioButton("Radio Button 3");
    JLabel label = new JLabel("Here is Status, look here."); // 不是按钮,是动态文本

    public TestButtons() {frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new java.awt.FlowLayout());

        // 为个别按钮增加动作监听器
        jButton.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent ae) {label.setText("You clicked jButton");
            }
        });

        // 为切换按钮增加动作监听器
        toggle.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent ae) {JToggleButton toggle = (JToggleButton) ae.getSource();
                if (toggle.isSelected()) {label.setText("You selected Toggle Button");
                } else {label.setText("You deselected Toggle Button");
                }
            }
        });

        // 为复选按钮增加条目监听器
        checkBox.addItemListener(new ItemListener() {public void itemStateChanged(ItemEvent e) {JCheckBox cb = (JCheckBox) e.getSource();
                label.setText("Selected Check Box is" + cb.isSelected());
            }
        });

        // 用一个按钮组对象容纳一组单选按钮
        ButtonGroup group = new ButtonGroup();
        // 生成一个新的动作监听器对象,备用
        ActionListener al = new ActionListener() {public void actionPerformed(ActionEvent ae) {JRadioButton radio = (JRadioButton) ae.getSource();
                if (radio == radio1) {label.setText("You selected Radio Button 1");
                } else if (radio == radio2) {label.setText("You selected Radio Button 2");
                } else {label.setText("You selected Radio Button 3");
                }
            }
        };
        // 为各单选按钮增加动作监听器
        radio1.addActionListener(al);
        radio2.addActionListener(al);
        radio3.addActionListener(al);
        // 将单选按钮增加到按钮组中
        group.add(radio1);
        group.add(radio2);
        group.add(radio3);

        frame.getContentPane().add(jButton);
        frame.getContentPane().add(toggle);
        frame.getContentPane().add(checkBox);
        frame.getContentPane().add(radio1);
        frame.getContentPane().add(radio2);
        frame.getContentPane().add(radio3);
        frame.getContentPane().add(label);

        frame.setSize(200, 250);
    }

    public void show() {frame.setVisible(true);
    }

}

除个别按钮外,其余三种按钮都有两种状态,即抉择 (按下) 状态和未抉择 (弹起) 状态。那么咱们又该如何判断呢?切换按钮 (JToggleButton) 提供了一个 isSelected() 办法用来判断以后所处的状态,返回值为真 (true) 时示意它处于抉择状态,返回值为假 (false) 时示意它处于未抉择状态。而复选按钮 (JCheckBox) 和单选按钮 (JRadioButton) 都是从 JToggleButton 继承的,所以也具备 isSelected() 办法。如上例中 if (toggle.isSelected()) {...} 等。

单选按钮由本身的特点决定了它们必须成组呈现,而且一组中只能有一个能被选中。因而咱们须要用一个专门的类,ButtonGroup 来治理。增加到 ButtonGroup 的多个单选按钮中,如果有一个被抉择中,同组中的其它单选按钮都会主动扭转其状态为未抉择状态。在 ButtonGroup 中增加按钮,是应用它的 add 办法,如上例中的 group.add(radio1)

既然咱们曾经将多个单选按钮增加到一个 ButtonGroup 中了,那么咱们是不是能够将一个蕴含多个单选按钮的 ButtonGroup 对象增加到 JFrame 的 Content Pane 中,以达到增加其中所有单选按钮的目标呢?不行!ButtonGroup 不是一个可显示的组件,它仅用于治理。所以,在往 JFrame 中增加一组 JRadioButton 的时候,须要一个一个的增加 JRadioButton,而不是抽象的增加一个 ButtonGroup

上例中还用到了 JLabel,这不是按钮,而是一个动态文本组件,次要用于显示提醒文本。要取得一个 JLabel 对象以后显示的文本内容,能够应用它的 getText() 办法;反之,要扭转一个 JLabel 对象显示的文本,要应用它的 setText(String text) 办法,如上例中的 label.setText("You selected Toggle Button")

其实这两个办法同样能够用于 JButton 等类。比方上例中咱们应用 new JButton("JButton") 结构了一个按钮 jButton,如果应用 jButton.getText() 就能够失去字符串 "JButton"。而 jButton.setText("A Button"),则能够扭转按钮上显示的文字为 "A Button"。这两句代码没有在示例中写进去,你能够本人试试。

上例中大量应用了动作监听器 (ActionListener)。ActionListener 只监听一个事件,这个事件在其相干组件上产生了动作时被触发,因而叫作动作事件 (ActionEvent)。ActionListener 只有一个办法须要实现,就是 actionPerformed(ActionEvent event)。按钮、切换按钮和单选按钮被单击时都会触发动作事件,引起动作监听器调用 actionPerformed 办法。因而,如果你想在单击按钮之后做什么事,当然应该重载 ActionListeneractionPerformed 办法了。各种按钮都提供了 addActionListener 办法以增加动作监听器。

2021-05-11 补充

Java 8 引入了 Lambda 语法,增加 ActionListener 能够用更简洁的语法,比方

jButton.addActionListener(e -> ((JRadioButton) e.getSource())
     .setText("add action listener by Lambda since java 8")
);

复选框就要非凡一些。尽管它也有 addActionListener 办法,意味着能够应用动作监听器,然而应用之后你会发现动作监听器并没有起到料想的作用。为什么?原来,单击一个复选按钮,触发的不是动作事件,而是条目事件 (ItemEvent) 中的状态变动 (itemStateChanged),由条目监听器 (ItemListener) 监听,相应须要重载的办法是 ItemListeneritemStateChanged 办法。

上例中咱们将一个名为 alActionListener 增加到了每一个单选按钮中,如何判断是哪个单选按钮触发了事件并被 al 监听到了呢?咱们能够从 ActionEventgetSource() 办法失去触发事件单选按钮。因为 getSource() 返回的是一个 Object 援用,尽管这个援用指向的是一个单选按钮的实例,但咱们还是须要将这个援用的类型转换为 JRadioButton,如上例中的:JRadioButton radio = (JRadioButton) ae.getSource(),只有这样咱们能力调用 JRadioButton 有而 Object 没有的办法。

同时,还须要阐明的一点是,每个单选按钮都能够增加一个独自的 ActionListener 实例,而不肯定要增加同一个。同样的情理,若干毫不相干的、须要增加 ActionListener 的若干组件,也能够增加同一个 ActionListener 实例。关键在于编程者对 actionPerformed 办法的重载。比方上面这段代码就为一个 JButton 对象和一个 JRadioButton 对象增加了同一个动作监听器实例:

/**
 * @(#) TestActionForDifferenctButtons.java
 * @author [James Fan](https://segmentfault.com/blog/jamesfancy)
 */

package jamesfan.uiDemo;

import javax.swing.*;
import java.awt.event.*;

public class TestActionForDifferenctButtons {

    JButton b;
    JRadioButton rb;

    public TestActionForDifferenctButtons() {JFrame f = new JFrame("Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new java.awt.FlowLayout());

        b = new JButton("JButton");
        rb = new JRadioButton("RadioButton");

        ActionListener a = new ActionListener() {public void actionPerformed(ActionEvent ae) {if (ae.getSource() == b) {System.out.println("You clicked the JButton");
                } else {System.out.println("You clicked the RadioButton");
                }
            }
        };

        b.addActionListener(a);
        rb.addActionListener(a);

        f.getContentPane().add(b);
        f.getContentPane().add(rb);
        f.pack();
        f.setVisible(true);
    }

    public static void main(String[] args) {new TestActionForDifferenctButtons();
    }

}

运行程序后,别离单击两个按钮,相应的,在控制台能别离失去如下输入:

You clicked the JButton
You clicked the RadioButton

这阐明多个不必的组件增加同一个监听器是可行的——不过前提是这些组件都能增加这个监听器。

四、文本输入框、明码输入框

文本输入框包含两种,单行文本输入框 (JTextField) 和多行文本输入框 (JTextArea)。明码输入框则只有一种 (JPasswordField)。JPasswordFieldJTextField 的子类,它们的次要区别是 JPasswordField 不会显示出用户输出的货色,而只会显示出程序员设定的一个固定字符,比方 ‘*’ 或者 ‘#’。

上面的示例图和代码是 JTextFieldJPasswordFieldJTextArea 的示例:

/**
 * @(#) TestTexts.java
 * @author [James Fan](https://segmentfault.com/blog/jamesfancy)
 */

package jamesfan.uiDemo;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;

public final class TestTexts extends JFrame {public static void main(String[] args) {TestTexts tt = new TestTexts();
        tt.setVisible(true);
    }

    private JLabel label = new JLabel("Status");
    private JTextField textField;
    private JPasswordField pwdField;
    private JTextArea textArea;

    public TestTexts() {super("Test Texts");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        getContentPane().setLayout(new java.awt.FlowLayout());

        textField = new JTextField(15);
        // 监听文本光标挪动事件
        textField.addCaretListener(new CaretListener() {public void caretUpdate(CaretEvent e) {
                // 如果扭转了内容,就能够即时更新 label 显示的内容
                label.setText(textField.getText());
            }
        });

        pwdField = new JPasswordField(15);
        pwdField.setEchoChar('#');

        textArea = new JTextArea(5, 15);
        textArea.setLineWrap(true);

        getContentPane().add(textField);
        getContentPane().add(pwdField);
        getContentPane().add(textArea);
        getContentPane().add(label);

        setSize(200, 200);
    }

}

上例中,咱们结构了一个宽度为 15 个字符的单行文本框 (textField = new JTextField(15)),并应用 addCaretListener 办法增加了一个 CaretListener (textField.addCaretListener ...)。CaretListener 监听文本光标的挪动事件。当用户应用键盘、鼠标等挪动了文本光标在 JTextField 中的地位时触发这个事件。咱们须要重载 caretUpdate(CaretEvent e) 对事件进行解决 (public void caretUpdate(CaretEvent e) ...)。这样,咱们能够在这里做相似 VB 中 TextBox 的 OnChange 事件中做的事件。

JTextField 有 5 个构造方法,罕用其中的 4 个:

  • JTextField()
  • JTextField(int columns),如上例 textField = new JTextField(15)
  • JTextField(String text)
  • JTextField(String text, int columns)

这些构造方法中,参数 text 是单行文本框的初始内容,而 columns 指定了单行文本框的宽度,以字符为单位。JTextField 中的文本内容能够用 getText 办法取得。也能够用 setText 办法指定 JTextField 中的文本内容。

JPasswordFieldJTextField 的子类,其构造方法也是相似的。JPasswordField 提供了 setEchoChar(char ch) 办法设置为了暗藏明码而显示的字符,默认为 '*' 字符,上例中则设置为了 '#' 字符 (pwdField.setEchoChar('#'))。与 JTextField 一样,JPasswordField 也用 getText 办法和 setText 取得或者设置文本内容 (当然在用户界面上是暗藏的)。

JTextField 是单行文本框,不能显示多行文本,如果想要显示多行文本,就只好应用多行文本框 JTextArea 了。JTextArea 有 6 个构造方法,罕用的也是 4 个:

  • JTextArea()
  • JTextArea(int rows, int columns)
  • JTextArea(String text)
  • JTextArea(String text, int rows, int columns)

textJTextArea 的初始化文本内容;rowsJTextArea 的高度,以行为单位;columnsJTextArea 的宽度,以字符为单位。如上例中就结构了一个高 5 行,宽 15 个字符的多行文本框 (textArea = new JTextArea(5, 15))。

多行文本框默认是不会主动折行的 (不过能够输出回车符换行),咱们能够应用 JTextAreasetLineWrap 办法设置是否容许主动折行。setLineWrap(true) 是容许主动折行,setLineWrap(false) 则是不容许主动折行。多行文本框会依据用户输出的内容主动扩大大小,不信,本人做个试验——如果不主动折行,那么多行文本框的宽度由最长的一行文字确定的;如果行数据超过了预设的行数,则多行文本框会扩大本身的高度去适应。换句话说,多行文本框不会主动产生滚动条。怎么办?前面讲到滚动窗格 (JScrollPane) 的时候,你就晓得了。

多行文本框里文本内容的取得和设置,同样能够应用 getTextsetText 两个办法来实现。

五、窗格、滚动窗格和布局治理

窗格 (JPanel) 和滚动窗格 (JScrollPane) 在图形用户界面设计中大量用于各种组件在窗口上的安排和安顿。这里所谓的安排和安顿,就是布局 (Layout),因而不得不先说说布局。

将退出到容器 (通常为窗口等) 的组件依照肯定的程序和规定搁置,使之看起来更好看,这就是布局。布局由布局管理器 (Layout Manager) 来治理。那么,咱们在什么时候应该应用布局管理器?利用抉择哪种布局管理器?又该怎么应用布局管理器呢?

往往,咱们设计一个窗口,其中是要增加若干组件的。为了治理好这些治理的布局,咱们就要应用布局管理器。比如说,设计一个简略的编辑器,这个编辑器中只须要搁置两个按钮和一个多行文本框。这些组件是让 Java 本人任意安顿呢?还是依照肯定的地位关系较标准的安顿呢?当然应该抉择后者。那么,为了依照肯定的地位关系安顿这些组件,咱们就须要用到布局管理器了。

而后咱们遇到了一个选择题——应用哪种布局管理器。为此,咱们首先要晓得有些什么布局管理器,它们的布局特点是什么。罕用的布局管理器有:FlowLayoutBorderLayoutGridLayoutBoxLayout 等,其中 FlowLayoutBorderLayout 最罕用,本文次要也就只谈谈这两种布局管理器。上面列表阐明它们的布局特点:

布局管理器 布局特点

Layout 类 简要阐明
FlowLayout 将组件按从左到右从上到下的程序顺次排列,一行不能放完则折到下一行持续搁置
BorderLayout 将组件按东 (右)、南 (下)、西 (左)、北 (上)、中五个区域搁置,每个方向最多只能搁置一个组件 (或容器)。
GridLayout 形似一个无框线的表格,每个单元格中放一个组件
BoxLayout 就像参差搁置的一行或者一列盒子,每个盒子中一个组件

就上述的编辑器为例,如果选用 FlowLayout,那么两个按钮和一个多行文本框就会排列在一行——当然这是窗口足够宽的状况;如果窗口稍窄一些,则可能分两行排列,第一行有两个按钮,而第二行是多行文本框 —— 这是最现实的状况;如果窗口再窄一些,就可能分三行排列,第一行和第二行别离搁置一个按钮,第三行搁置多行文本框。因而,如果窗口大小能够扭转,那么三个组件的地位关系也可能随着窗口大小的变动而变动。其实下面所举的例程中,大部分都是用的 FlowLayout,那是因为咱们没有要求组件的布局。

如果选用 BorderLayout 的状况又如何呢?咱们能够试着退出一个窗格 (JPanel,稍后解说 ),并将两个按钮搁置在其中,而后将这个窗格退出到 BorderLayout 的北部 (即上部);再将多行文本框退出到 BorderLayout 中部。后果相似应用 FlowLayout 的第二种可能,是最现实的状况。而且,如果扭转窗口大小,它们的地位关系依然是北 - 中的关系,不会随之扭转。

剩下的两种布局管理器,加以窗格 (JPanel) 的配合,也可能很好的安顿上述编辑器所需的三个组件。然而因为它们的应用稍为简单一些,所以就不讲了。上面就讲讲如何应用 FlowLayoutBorderLayout

任何布局管理器,都须要用在容器上,比方 JFrame 的 Content Pane 和上面要说的 JPanel 都是容器 (JFrame 默认的 Content Pane 理论就是一个 JPanel)。容器组件提供了一个 setLayout 办法,就是用来扭转其布局管理器的。默认状况下,JFrame 的 Content Pane 应用的是 BorderLayout,而一个新产生的 JPanel 对象应用的是 FlowLayout。但不管怎样,咱们都能够调用它们的 setLayout 办法来扭转其布局管理器。比方上述的编辑器中,咱们要让窗口 (JFrame 对象,假如为 frame) 应用 BorderLayout,就能够应用 frame.getContentPane().setLayout(new BorderLayout()) 来扭转其布局管理器为一个新的 BorderLayout 对象。

而后,咱们对布局管理器的间接操作就完结了,剩下的只须要往容器里增加组件。如果应用 FlowLayout,咱们只须要应用容器的 add(Component c) 办法增加组件就行了。然而,如果应用 BorderLayout 就不一样了,因为要指定是把组件增加到哪个区域啊。那咱们就应用容器的 add(Component c, Object o) 办法增加组件,该办法的第二个参数就是指明增加到的区域用的。例如,上述编辑器中要增加一个多行文本框到 BorderLayout 的中部,就能够用 frame.getContentPane().add(new JTextArea(5, 15), BorderLayout.CENTER) 来实现。

BorderLayout 的五个区域别离是用下列五个常量来形容的:

常量名称 含意
BorderLayout.EAST 东 (右)
BorderLayout.SOUTH 南 (下)
BorderLayout.WEST 西 (左)
BorderLayout.NORTH 北 (上)
BorderLayout.CENTER

方才曾经提到了应用 JPanelJPanel 作为一个容器,能够容纳一些组件,而后将这个 JPanel 对象作为一个组件增加到另一个容器 (称作父容器) 中。这个性能有什么益处呢?

下面不是提到 BorderLayout 的一个区域中只能增加一个组件吗?然而咱们的编辑器须要增加两个按钮到它的北部,怎么办?上面的例子中,咱们就会用的一个 JPanel 容纳了这两个按钮,而后再将这个 JPanel 对象作为一个组件增加到设置布局管理器为 BorderLayout 的 Content Pane 中。

下面说到各布局管理器的布局特点的时候,简直每一种都是一个区域只能增加一个组件,那咱们想增加多个组件到一个区域的时候,就要用到 JPanel 了。如果还没有明确,稍后看一段程序可能更易于了解。

而滚动窗格 (JScrollPane) 呢?它是一个可能本人产生滚动条的容器,通常只容纳一个组件,并且依据这个组件的大小主动产生滚动条。比方下面讲 JTextArea 的时候提到:JTextAera 会随用户输出的内容主动扩大大小,很容易突破各组件的布局。然而,如果咱们将它容纳在一个滚动窗格中,它的扩大就不会间接反映在大小的变动上,而会反映在滚动窗格的滚动条上,也就不会突破各组件的布局了。稍后的例子会让你清清楚楚。

是不是等着看例子了?好,例子来了:

/**
 * @(#) TestPanels.java
 * @author [James Fan](https://segmentfault.com/blog/jamesfancy)
 */

package jamesfan.uiDemo;

import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public final class TestPanels extends JFrame {public static void main(String[] args) {TestPanels tp = new TestPanels();
        tp.setVisible(true);
    }

    public TestPanels() {setDefaultCloseOperation(EXIT_ON_CLOSE);
        JPanel panel = new JPanel();

        for (int i = 0; i < 2; i++) {panel.add(new JButton("Button 00" + i));
        }

        JTextArea textArea = new JTextArea(5, 15);
        textArea.setLineWrap(true);

        JScrollPane scrollPane = new JScrollPane(textArea);

        getContentPane().add(panel, BorderLayout.NORTH);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        pack();}

}

这个例子的运行后果如下图,正是咱们想要的后果——下面两个按钮,上面是一个能够滚动的多行文本框:

上例中首先产生了一个 JPanel 对象 (JPanel panel = new JPanel()),而后将两个按钮置于其中 (panel.add ...);而后产生了一个多行文本框 (JTextArea textArea = new JTextArea(5, 15)),并应用一个滚动窗格将它包裹起来 (JScrollPane scrollPane = new JScrollPane(textArea)),使之成为能够滚动的多行文本框。最初将两个容器 (JPanel 对象和 JScrollPane 对象 ) 别离增加到了窗口的北部 (getContentPane().add(panel, BorderLayout.NORTH)) 和中部 (也就是残余局部,getContentPane().add(scrollPane, BorderLayout.CENTER))。

如同有点不对劲,是什么呢?对了,咱们没有设置 Content Pane 的布局管理器为 BorderLayout 啊,为什么……方才不是说了吗,JFrame 的 Content Pane 的默认布局管理器就是 BorderLayout,所以不必再设置了。

好了,《简述 Java 图形用户界面设计》就告一段落了。因为篇幅无限,这里说的都是高级常识,有此基础,设计简单一点的图形用户界面也就不是难事了!

退出移动版