ZetCode

Swing 布局管理

最后修改于 2023 年 1 月 10 日

Java Swing 有两种组件:容器和子组件。容器将子组件分组到合适的布局中。为了创建布局,我们使用*布局管理器*。

ZetCode 提供了一本专门的 196 页 电子书,用于 Swing 布局管理过程:Java Swing 布局管理教程

Swing 布局管理器

Swing 拥有大量的布局管理器——内置和第三方。然而,大多数管理器不适用于现代 UI 的创建。

有三个布局管理器可以正确地完成这项工作

MigLayoutGroupLayoutFormLayout 是强大、灵活的布局管理器,可以满足大多数布局要求。在本教程中,我们使用 GroupLayout 管理器来设计用户界面。

以下布局管理器已过时

这些布局管理器无法满足现代 UI 的要求。

过时管理器的缺点

过时的管理器要么过于简单 (FlowLayout, GridLayout),要么不必要地复杂 (GridBagLayout)。所有这些管理器都有一个基本的错误设计:*它们使用组件之间的固定间隙*。使用组件之间的刚性空间并不可移植:一旦程序在不同的屏幕分辨率上运行,用户界面就会被破坏。

过时的管理器试图通过一种称为嵌套的技术来修复它们的弱点。在嵌套中,开发人员在多个面板中使用几个不同的布局管理器。虽然可以使用嵌套创建 UI,但它给代码带来了不必要的复杂性。

过时的管理器

在本节中,我们将介绍过时的布局管理器。不推荐使用这些管理器。只有在需要维护一些遗留代码时,才花一些时间来研究它们。否则,应该拒绝使用它们。

FlowLayout 管理器

这是 Java Swing 工具包中最简单的布局管理器。它是 JPanel 组件的默认布局管理器。

它非常简单,无法用于任何实际的布局。这个管理器在许多 Java Swing 教程中都有介绍,因此,初学者试图在他们的项目中也使用它,但没有意识到它不能用于任何严肃的事情。

在计算其子组件大小时,流式布局允许每个组件采用其自然 (首选) 的大小。该管理器将组件放入一行。按照添加的顺序。如果它们不适合一行,则进入下一行。组件可以从右向左或从左向右添加。该管理器允许对齐组件。隐式地,组件居中放置,并且组件之间以及组件和容器边缘之间有 5px 的空间。

FlowLayout()
FlowLayout(int align)
FlowLayout(int align, int hgap, int vgap) 

FlowLayout 管理器有三个构造函数可用。第一个构造函数使用隐式值创建一个管理器。居中对齐,水平和垂直间距均为 5px。其他构造函数允许指定这些参数。

com/zetcode/FlowLayoutEx.java
package com.zetcode;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTree;
import java.awt.Dimension;
import java.awt.EventQueue;

public class FlowLayoutEx extends JFrame {

    public FlowLayoutEx() {

        initUI();
    }

    private void initUI() {

        var panel = new JPanel();

        var button = new JButton("button");
        panel.add(button);

        var tree = new JTree();
        panel.add(tree);

        var area = new JTextArea("text area");
        area.setPreferredSize(new Dimension(100, 100));

        panel.add(area);

        add(panel);

        pack();

        setTitle("FlowLayout example");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new FlowLayoutEx();
            ex.setVisible(true);
        });
    }
}

该示例显示窗口中的一个按钮、一个树组件和一个文本区域组件。如果我们创建一个空的树组件,则该组件内部有一些默认值。

var panel = new JPanel();

JPanel 组件的隐式布局管理器是 FlowLayout。我们不必手动设置它。

var area = new JTextArea("text area");
area.setPreferredSize(new Dimension(100, 100));

流式布局管理器为其组件设置了*首选*大小。因此,在我们的例子中,区域组件将是 100x100px。如果我们没有设置首选大小,组件将具有其文本的大小。如果没有文本,组件将根本不可见。尝试在区域组件中写入或删除一些文本。组件将相应地增长和缩小。

panel.add(area);

该组件使用 add() 放置在容器内。

FlowLayout
图:FlowLayout

GridLayout

GridLayout 布局管理器将组件布置在一个矩形网格中。容器被分成大小相等的矩形。每个矩形放置一个组件。

GridLayout 非常简单,不能用于任何实际的布局。

com/zetcode/GridLayoutEx.java
package com.zetcode;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.EventQueue;
import java.awt.GridLayout;

public class GridLayoutEx extends JFrame {

    public GridLayoutEx() {

        initUI();
    }

    private void initUI() {

        var panel = new JPanel();

        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        panel.setLayout(new GridLayout(5, 4, 5, 5));

        String[] buttons = {
                "Cls", "Bck", "", "Close", "7", "8", "9", "/", "4",
                "5", "6", "*", "1", "2", "3", "-", "0", ".", "=", "+"
        };

        for (int i = 0; i < buttons.length; i++) {

            if (i == 2) {
                panel.add(new JLabel(buttons[i]));
            } else {
                panel.add(new JButton(buttons[i]));
            }
        }

        add(panel);

        setTitle("GridLayout");
        setSize(350, 300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new GridLayoutEx();
            ex.setVisible(true);
        });
    }
}

该示例显示了一个简单计算器工具的框架。我们将 19 个按钮和一个标签放入管理器中。请注意,每个按钮的大小都相同。

panel.setLayout(new GridLayout(5, 4, 5, 5));

在这里,我们为面板组件设置了网格布局管理器。布局管理器接受四个参数。行数、列数以及组件之间的水平和垂直间隙。

GridLayout
图:GridLayout

BorderLayout

BorderLayout 是一个简单的布局管理器,在某些布局中可能很有用。它是 JFrameJWindowJDialogJInternalFrameJApplet 的默认布局管理器。它有一个严重的限制——它以像素为单位设置其子组件之间的间隙,从而创建刚性布局。这导致 UI 不可移植,因此不推荐使用。

BorderLayout 将空间划分为五个区域:北、西、南、东和中心。每个区域只能有一个组件。如果我们需要将更多组件放入一个区域,我们必须在那里放置一个带有我们选择的管理器 的面板。N、W、S、E 区域中的组件获得它们的*首选*大小。中心的组件占据剩余的整个空间。

如果子组件彼此靠得太近,它看起来不太好。我们必须在它们之间留一些空间。Swing 工具包中的每个组件都可以在其边缘周围添加边框。要创建边框,我们既可以创建一个新的 EmptyBorder 类实例,也可以使用 BorderFactory

com/zetcode/BorderEx.java
package com.zetcode;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;

public class BorderLayoutEx extends JFrame {

    public BorderLayoutEx() {

        initUI();
    }

    private void initUI() {

        var bottomPanel = new JPanel(new BorderLayout());
        var topPanel = new JPanel();

        topPanel.setBackground(Color.gray);
        topPanel.setPreferredSize(new Dimension(250, 150));
        bottomPanel.add(topPanel);

        bottomPanel.setBorder(new EmptyBorder(new Insets(20, 20, 20, 20)));

        add(bottomPanel);

        pack();

        setTitle("BorderLayout");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new BorderLayoutEx();
            ex.setVisible(true);
        });
    }
}

该示例将显示一个灰色的面板及其周围的边框。

var bottomPanel = new JPanel(new BorderLayout());
var topPanel = new JPanel();

我们将一个面板放置到另一个面板中。底面板具有 BorderLayout 管理器。

bottomPanel.add(topPanel);

在这里,我们将顶部面板放置到底部面板组件中。更准确地说,我们将其放置在其 BorderLayout 管理器的中心区域中。

bottomPanel.setBorder(new EmptyBorder(new Insets(20, 20, 20, 20)));

在这里,我们在底面板周围创建了一个 20px 的边框。边框值如下:顶部、左侧、底部和右侧。请注意,创建固定内边距(空格)是不可移植的。

BorderLayout
图:BorderLayout

下一个示例显示了 BorderLayout 管理器的典型用法。

com/zetcode/BorderLayoutEx2.java
package com.zetcode;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Insets;

public class BorderLayoutEx2 extends JFrame {

    public BorderLayoutEx2() {

        initUI();
    }

    private void initUI() {

        var menubar = new JMenuBar();
        var fileMenu = new JMenu("File");

        menubar.add(fileMenu);
        setJMenuBar(menubar);

        var toolbar = new JToolBar();
        toolbar.setFloatable(false);

        var exitIcon = new ImageIcon("src/resources/exit.png");
        var exitBtn = new JButton(exitIcon);
        exitBtn.setBorder(new EmptyBorder(0, 0, 0, 0));
        toolbar.add(exitBtn);

        add(toolbar, BorderLayout.NORTH);

        var vertical = new JToolBar(JToolBar.VERTICAL);
        vertical.setFloatable(false);
        vertical.setMargin(new Insets(10, 5, 5, 5));

        var driveIcon = new ImageIcon("src/resources/drive.png");
        var compIcon = new ImageIcon("src/resources/computer.png");
        var printIcon = new ImageIcon("src/resources/printer.png");

        var driveBtn = new JButton(driveIcon);
        driveBtn.setBorder(new EmptyBorder(3, 0, 3, 0));

        var compBtn = new JButton(compIcon);
        compBtn.setBorder(new EmptyBorder(3, 0, 3, 0));
        var printBtn = new JButton(printIcon);
        printBtn.setBorder(new EmptyBorder(3, 0, 3, 0));

        vertical.add(driveBtn);
        vertical.add(compBtn);
        vertical.add(printBtn);

        add(vertical, BorderLayout.WEST);

        add(new JTextArea(), BorderLayout.CENTER);

        var statusbar = new JLabel(" Statusbar");
        add(statusbar, BorderLayout.SOUTH);

        setSize(400, 350);
        setTitle("BorderLayout");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
        
            var ex = new BorderLayoutEx2();
            ex.setVisible(true);
        });
    }
}

该示例显示了一个典型的应用程序框架。我们显示了一个垂直和水平工具栏、一个状态栏和一个中心组件(一个文本区域)。

BorderLayoutJFrame 容器的默认布局管理器。所以我们不必显式地设置它。

add(toolbar, BorderLayout.NORTH);

我们将工具栏放置在布局的上方。

var driveBtn = new JButton(driveIcon);
driveBtn.setBorder(new EmptyBorder(3, 0, 3, 0));

为了在按钮周围留出一些空白空间,我们必须使用 EmptyBorder。这为按钮的顶部和底部添加了一些固定空间。当我们添加固定空间时,UI 不可移植。在 1280x720 屏幕上,3 像素的空间可能看起来很好,但在 1920x1200 像素的屏幕上,它是不合适的。

add(vertical, BorderLayout.WEST); 

我们将垂直工具栏放置在西侧。

add(new JTextArea(), BorderLayout.CENTER);

我们将文本区域放置在中心。

add(statusbar, BorderLayout.SOUTH);

状态栏进入南侧区域。

BorderLayout 2
图:BorderLayout 2

CardLayout

CardLayout 是一个简单的布局管理器,它将每个组件视为一张卡片。容器是这些卡片的堆叠。一次只显示一个组件;其余的被隐藏。添加到容器的第一个组件在容器最初显示时默认可见。此管理器的实际用途有限。它可用于创建向导或选项卡窗格。

以下示例使用 CardLayout 管理器创建一个图像库。我们使用了四张克拉斯纳霍尔卡城堡的图片(2012 年火灾之前)。

com/zetcode/CardLayoutEx.java
package com.zetcode;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.EventQueue;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class CardLayoutEx extends JFrame {

    private ImageIcon horka1;
    private ImageIcon horka2;
    private ImageIcon horka3;
    private ImageIcon horka4;
    private ImageIcon previ;
    private ImageIcon nexti;

    private JPanel mainPanel;
    private CardLayout cardLayout;

    public CardLayoutEx() {

        initUI();
    }

    private void initUI() {

        mainPanel = new JPanel();
        mainPanel.setBackground(new Color(50, 50, 50));

        mainPanel.setBorder(
                BorderFactory.createEmptyBorder(5, 5, 5, 5)
        );

        cardLayout = new CardLayout();
        mainPanel.setLayout(cardLayout);

        horka1 = new ImageIcon("src/resources/horka1.jpg");
        horka2 = new ImageIcon("src/resources/horka2.jpg");
        horka3 = new ImageIcon("src/resources/horka3.jpg");
        horka4 = new ImageIcon("src/resources/horka4.jpg");

        previ = new ImageIcon("src/resources/previous.png");
        nexti = new ImageIcon("src/resources/next.png");

        var label1 = new JLabel(horka1);
        var label2 = new JLabel(horka2);
        var label3 = new JLabel(horka3);
        var label4 = new JLabel(horka4);

        mainPanel.add(label1);
        mainPanel.add(label2);
        mainPanel.add(label3);
        mainPanel.add(label4);

        add(mainPanel);

        var prevButton = new JButton(previ);
        prevButton.addActionListener((e) -> cardLayout.previous(mainPanel));

        var nextButton = new JButton(nexti);
        nextButton.addActionListener((e) -> cardLayout.next(mainPanel));

        var btnPanel = new JPanel();
        btnPanel.setBackground(new Color(50, 50, 50));
        btnPanel.add(prevButton);
        btnPanel.add(nextButton);

        add(btnPanel, BorderLayout.SOUTH);

        pack();

        setTitle("Gallery");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
        
            var ex = new CardLayoutEx();
            ex.setVisible(true);
        });
    }
}

我们创建了两个按钮来浏览图片。

mainPanel = new JPanel();
mainPanel.setBackground(new Color(50, 50, 50));

mainPanel.setBorder(
        BorderFactory.createEmptyBorder(5, 5, 5, 5)
);

我们创建了主面板组件。我们将它的颜色设置为深灰色。我们在面板周围放置 5px,这样它的子组件就不会离窗口的边框太近。

cardLayout = new CardLayout();
mainPanel.setLayout(cardLayout);

创建 CardLayout 管理器并将其设置为主面板。

mainPanel.add(label1);
mainPanel.add(label2);
mainPanel.add(label3);
mainPanel.add(label4);

显示图像的标签组件被添加到面板中。

var prevButton = new JButton(previ);
prevButton.addActionListener((e) -> cardLayout.previous(mainPanel));

单击“上一个”按钮将调用管理器的 previous() 方法。它翻转到指定容器的上一张卡片。

add(mainPanel);

我们将主面板添加到框架组件的边框布局的中心区域。如果我们没有明确指定放置组件的位置,则将其添加到中心区域。

var btnPanel = new JPanel();
btnPanel.setBackground(new Color(50, 50, 50));
btnPanel.add(prevButton);
btnPanel.add(nextButton);

按钮被添加到按钮面板。

add(btnPanel, BorderLayout.SOUTH);

最后,带有按钮的面板被放置到 BorderLayout 管理器的南侧区域。

CardLayout
图:CardLayout

BoxLayout

BoxLayout 管理器是一个简单的布局管理器,它以列或行的形式组织组件。它可以通过嵌套创建相当复杂的布局。但是,这增加了布局创建的复杂性,并使用了额外的资源,特别是许多其他 JPanel 组件。BoxLayout 只能创建固定空间;因此,它的布局不可移植。

BoxLayout 具有以下构造函数

BoxLayout(Container target, int axis)

构造函数创建一个布局管理器,该管理器将沿给定的轴布置组件。与其他布局管理器不同,BoxLayout 将容器实例作为构造函数中的第一个参数。第二个参数确定管理器的方向。要创建一个水平框,我们可以使用 LINE_AXIS 常量。要创建一个垂直框,我们可以使用 PAGE_AXIS 常量。

框布局管理器通常与 Box 类一起使用。这个类创建了几个不可见的组件,这些组件会影响最终的布局。

假设我们想将两个按钮放到窗口的右下角。

com/zetcode/BoxLayoutButtonsEx.java
package com.zetcode;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.EventQueue;

public class BoxLayoutButtonsEx extends JFrame {

    public BoxLayoutButtonsEx() {

        initUI();
    }

    private void initUI() {

        var basePanel = new JPanel();
        basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
        add(basePanel);

        basePanel.add(Box.createVerticalGlue());

        var bottomPanel = new JPanel();
        bottomPanel.setAlignmentX(1f);
        bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));

        var okBtn = new JButton("OK");
        var closeBtn = new JButton("Close");

        bottomPanel.add(okBtn);
        bottomPanel.add(Box.createRigidArea(new Dimension(5, 0)));
        bottomPanel.add(closeBtn);
        bottomPanel.add(Box.createRigidArea(new Dimension(15, 0)));

        basePanel.add(bottomPanel);
        basePanel.add(Box.createRigidArea(new Dimension(0, 15)));

        setTitle("Two Buttons");
        setSize(300, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new BoxLayoutButtonsEx();
            ex.setVisible(true);
        });
    }
}

以下绘图说明了该示例。

Two buttons
图:两个按钮

我们创建了两个面板。底面板具有垂直框布局。底面板有一个水平框布局。我们将底面板放入基面板。底面板右对齐。窗口顶部和底面板之间的空间是可扩展的。这是通过垂直胶水实现的。

basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));

在这里,我们使用垂直 BoxLayout 创建一个基面板。

var bottomPanel = new JPanel();
bottomPanel.setAlignmentX(1f);
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));

底面板右对齐。这是通过 setAlignmentX() 方法完成的。面板具有水平布局。

bottomPanel.add(Box.createRigidArea(new Dimension(5, 0)));

我们在按钮之间放置一些刚性空间。

basePanel.add(bottomPanel);

在这里,我们将带有水平框布局的底面板放入垂直基面板。

basePanel.add(Box.createRigidArea(new Dimension(0, 15)));

我们还在底面板和窗口边框之间留出了一些空间。

BoxLayout buttons example
图:BoxLayout 按钮示例

当我们使用 BoxLayout 管理器时,我们可以在组件之间设置一个刚性区域。

com/zetcode/BoxLayoutRigidAreaEx.java
package com.zetcode;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;

public class BoxLayoutRigidAreaEx extends JFrame {

    public BoxLayoutRigidAreaEx() {

        initUI();
    }

    private void initUI() {

        var basePanel = new JPanel();
        basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));

        basePanel.setBorder(new EmptyBorder(new Insets(40, 60, 40, 60)));

        basePanel.add(new JButton("Button"));
        basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
        basePanel.add(new JButton("Button"));
        basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
        basePanel.add(new JButton("Button"));
        basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
        basePanel.add(new JButton("Button"));

        add(basePanel);

        pack();

        setTitle("RigidArea");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
        
            var ex = new BoxLayoutRigidAreaEx();
            ex.setVisible(true);
        });
    }
}

在此示例中,我们显示了四个按钮。默认情况下,按钮之间没有空间。要在它们之间放置一些空间,我们添加一些刚性区域。

basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));

我们为面板使用垂直 BoxLayout 管理器。

basePanel.add(new JButton("Button"));
basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
basePanel.add(new JButton("Button"));

我们添加按钮并在它们之间使用 Box.createRigidArea() 创建一个刚性区域。

Rigid area
图:刚性区域

每日提示

下一个示例创建了一个每日提示窗口对话框。我们使用了各种布局管理器的组合。

com/zetcode/TipOfDayEx.java
package com.zetcode;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextPane;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.KeyEvent;


public class TipOfDayEx extends JDialog {

    public TipOfDayEx() {

        initUI();
    }

    private void initUI() {

        var basePanel = new JPanel();
        basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
        add(basePanel);

        var topPanel = new JPanel(new BorderLayout(0, 0));
        topPanel.setMaximumSize(new Dimension(450, 0));

        var hint = new JLabel("Productivity Hints");
        hint.setBorder(BorderFactory.createEmptyBorder(0, 25, 0, 0));
        topPanel.add(hint);

        var icon = new ImageIcon("src/resources/coffee2.png");
        var label = new JLabel(icon);
        label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        topPanel.add(label, BorderLayout.EAST);

        var separator = new JSeparator();
        separator.setForeground(Color.gray);

        topPanel.add(separator, BorderLayout.SOUTH);

        basePanel.add(topPanel);

        var textPanel = new JPanel(new BorderLayout());
        textPanel.setBorder(BorderFactory.createEmptyBorder(15, 25, 15, 25));

        var pane = new JTextPane();
        pane.setContentType("text/html");
        var text = "<p><b>Closing windows using the mouse wheel</b></p>" +
             "<p>Clicking with the mouse wheel on an editor tab closes the window. " +
             "This method works also with dockable windows or Log window tabs.</p>";
        pane.setText(text);
        pane.setEditable(false);
        textPanel.add(pane);

        basePanel.add(textPanel);

        var boxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 0));

        var box = new JCheckBox("Show Tips at startup");
        box.setMnemonic(KeyEvent.VK_S);

        boxPanel.add(box);
        basePanel.add(boxPanel);

        var bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));

        var tipBtn = new JButton("Next Tip");
        tipBtn.setMnemonic(KeyEvent.VK_N);

        var closeBtn = new JButton("Close");
        closeBtn.setMnemonic(KeyEvent.VK_C);

        bottomPanel.add(tipBtn);
        bottomPanel.add(closeBtn);
        basePanel.add(bottomPanel);

        bottomPanel.setMaximumSize(new Dimension(450, 0));

        setTitle("Tip of the Day");
        setSize(new Dimension(450, 350));
        setResizable(false);
        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        setLocationRelativeTo(null);
    }


    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
        
            var ex = new TipOfDayEx();
            ex.setVisible(true);
        });
    }
}

该示例使用混合的布局管理器。简单地说,我们将四个面板放入垂直组织的基面板中。

var basePanel = new JPanel();
basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
add(basePanel);

这是最底部的面板。它有一个垂直框布局管理器。基面板被添加到默认的 JDialog 组件中。此组件默认具有边框布局管理器。

var topPanel = new JPanel(new BorderLayout(0, 0));

topPanel 面板有一个边框布局管理器。我们将在其中放入三个组件。两个标签和一个分隔符。

topPanel.setMaximumSize(new Dimension(450, 0));

如果我们希望面板不大于其组件,则必须设置其最大大小。忽略零值。管理器计算必要的高度。

var textPanel = new JPanel(new BorderLayout());
...
textPanel.add(pane);

文本窗格组件被添加到边框布局管理器的中心区域。它占据了所有剩余的空间。确切地说,正如我们希望的那样。

var boxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 0));

复选框显示在 boxPanel 面板中。它左对齐。流式布局管理器具有 20px 的水平间隙。其他组件有 25px。为什么会这样?这是因为流式布局管理器也会在组件和边缘之间留出一些空间。

var bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
...
bottomPanel.setMaximumSize(new Dimension(450, 0));

底面板显示两个按钮。它有一个右对齐的流式布局管理器。为了在对话框的右边缘显示按钮,面板必须从头到尾水平延伸。

Tip of the Day
图:每日提示

没有管理器

可以不使用布局管理器。在少数情况下,我们可能不需要布局管理器。(也许将几个图像放置在一些不规则的位置。)但在大多数情况下,为了创建真正可移植的复杂应用程序,我们需要布局管理器。

如果没有布局管理器,我们使用绝对值来定位组件。

com/zetcode/AbsoluteLayoutEx.java
package com.zetcode;

import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.EventQueue;

public class AbsoluteLayoutEx extends JFrame {

    public AbsoluteLayoutEx() {

        initUI();
    }

    private void initUI() {

        setLayout(null);

        var okBtn = new JButton("OK");
        okBtn.setBounds(50, 50, 80, 25);

        var closeBtn = new JButton("Close");
        closeBtn.setBounds(150, 50, 80, 25);

        add(okBtn);
        add(closeBtn);

        setTitle("Absolute positioning");
        setSize(300, 250);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
        
            var ex = new AbsoluteLayoutEx();
            ex.setVisible(true);
        });
    }
}

这个简单的例子显示了两个按钮。

setLayout(null);

我们通过向 setLayout() 方法提供 null 来使用绝对定位。(JFrame 组件有一个默认的布局管理器,即 BorderLayout。)

okBtn.setBounds(50, 50, 80, 25);

setBounds() 方法定位“确定”按钮。参数是组件的 x 和 y 坐标以及宽度和高度。

Absolute layout
图:绝对布局

在本章中,我们提到了 Swing 中的布局管理。