ZetCode

GroupLayout 管理器

最后修改于 2023 年 1 月 10 日

GroupLayout 管理器是一个内置的 Swing 管理器。它是唯一一个可以创建多平台布局的内置管理器。所有其他管理器要么非常简单,要么使用固定大小的间隙,这些间隙不适用于不同平台和屏幕分辨率的用户界面。除了 GroupLayout,我们还可以使用第三方 MigLayout 在 Java 中创建多平台布局。

ZetCode 提供了一本专门针对 Swing 布局管理流程的 196 页 电子书:《Java Swing 布局管理教程

GroupLayout 描述

GroupLayout 将组件与实际布局分开;所有组件都可以在一个地方设置,而布局可以在另一个地方设置。

GroupLayout 管理器独立地为每个维度定义布局。在一个维度中,我们将组件沿水平轴放置;在另一个维度中,我们将组件沿垂直轴放置。在两种布局中,我们可以按顺序或并行排列组件。在水平布局中,一排组件称为顺序组,一列组件称为并行组。在垂直布局中,一列组件称为顺序组,一排组件称为并行组。

GroupLayout 间隙

GroupLayout 在组件之间或组件和边框之间使用三种类型的间隙:RELATED(相关), UNRELATED(不相关) 和 INDENTED(缩进)。RELATED 用于相关组件,UNRELATED 用于不相关组件,INDENTED 用于组件之间的缩进。这些间隙的主要优点是它们与分辨率无关;即它们在不同分辨率的屏幕上具有不同的像素大小。其他内置管理器错误地在所有分辨率上使用固定大小的间隙。

看到只有三个预定义的间隙可能会令人惊讶。在 LaTeX(一种高质量的排版系统)中,只有三个垂直空间可用:\smallskip, \medskip\bigskip。在设计用户界面时,少即是多,仅仅因为我们可以使用许多不同的间隙大小、字体大小或颜色,并不意味着我们应该这样做。

GroupLayout 简单示例

组件使用 addComponent() 方法添加到布局管理器中。参数是最小、首选和最大大小值。我们可以传递一些特定的绝对值,或者可以提供 GroupLayout.DEFAULT_SIZEGroupLayout.PREFERRED_SIZEGroupLayout.DEFAULT_SIZE 表示应使用组件的相应大小(例如,对于最小参数,该值由 getMinimumSize() 方法确定)。以类似的方式,GroupLayout.PREFERRED_SIZE 通过调用组件的 getPreferredSize() 方法来确定

com/zetcode/GroupLayoutSimpleEx.java
package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.Alignment.LEADING;
import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;

public class GroupLayoutSimpleEx extends JFrame {

    public GroupLayoutSimpleEx() {

        initUI();
    }

    private void initUI() {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        var lbl = new JLabel("Name:");
        var field = new JTextField(15);

        GroupLayout.SequentialGroup sg = gl.createSequentialGroup();

        sg.addComponent(lbl).addPreferredGap(RELATED).addComponent(field,
                GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
                GroupLayout.PREFERRED_SIZE);

        gl.setHorizontalGroup(sg);

        GroupLayout.ParallelGroup pg = gl.createParallelGroup(
                LEADING, false);

        pg.addComponent(lbl).addComponent(field);
        gl.setVerticalGroup(pg);

        gl.setAutoCreateContainerGaps(true);

        pack();

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

    public static void main(String[] args) {

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

在示例中,我们有一个标签和一个文本字段。文本字段不可伸缩。

GroupLayout.SequentialGroup sg = gl.createSequentialGroup();

sg.addComponent(lbl).addPreferredGap(RELATED).addComponent(field,
        GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
        GroupLayout.PREFERRED_SIZE);

addComponent()addPreferredGap() 方法都返回组对象;因此,可以创建一个方法调用链。我们将文本字段的最大大小更改为 GroupLayout.PREFERRED_SIZE,从而使其在水平方向上无法扩展到其首选大小之外。(首选大小和最大大小之间的差异是组件的增长趋势。这适用于尊重这些值的管理器。)将该值改回 GroupLayout.DEFAULT_SIZE 将导致文本字段在水平方向上扩展。

GroupLayout.ParallelGroup pg = gl.createParallelGroup(
        LEADING, false);

在垂直布局中,createParallelGroup() 为其第二个参数接收 false。通过这种方式,我们阻止文本字段在垂直方向上增长。这可以通过将 addComponent() 的最大参数设置为 GroupLayout.PREFERRED_SIZE 来实现,该参数在垂直布局中被调用。

GroupLayout simple example
图:GroupLayout 简单示例

GroupLayout 基线对齐

基线对齐是将组件沿其包含文本的基线对齐。以下示例将两个组件沿其基线对齐。

com/zetcode/GroupLayoutBaselineEx.java
package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.Alignment.BASELINE;

public class GroupLayoutBaselineEx extends JFrame {

    private JLabel display;
    private JComboBox<String> box;
    private String[] distros;

    public GroupLayoutBaselineEx() {

        initUI();
    }

    private void initUI() {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        distros = new String[] {"Easy", "Medium", "Hard"};
        box = new JComboBox<>(distros);
        display = new JLabel("Level:");

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(display)
                .addComponent(box,
                        GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE,
                        GroupLayout.PREFERRED_SIZE)
        );

        gl.setVerticalGroup(gl.createParallelGroup(BASELINE)
                .addComponent(box, GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE,
                        GroupLayout.PREFERRED_SIZE)
                .addComponent(display)
        );

        pack();

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

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new GroupLayoutBaselineEx();
            ex.setVisible(true);
        });
    }
}

我们有一个标签和一个组合框。这两个组件都包含文本。我们将这两个组件沿其文本的基线对齐。

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addComponent(display)
        .addComponent(box,
                GroupLayout.DEFAULT_SIZE,
                GroupLayout.DEFAULT_SIZE,
                GroupLayout.PREFERRED_SIZE)
);

基线对齐是通过将 BASELINE 参数传递给 createParallelGroup() 方法来实现的。

GroupLayout baseline alignment
图:GroupLayout 基线对齐

GroupLayout 角落按钮示例

以下示例将两个按钮放置在窗口的右下角。按钮设置为相同大小。

com/zetcode/GroupLayoutCornerButtonsEx.java
package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingConstants;
import java.awt.Dimension;
import java.awt.EventQueue;

import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;

public class GroupLayoutCornerButtonsEx extends JFrame {

    public GroupLayoutCornerButtonsEx() {

        initUI();
    }

    private void initUI() {

        setPreferredSize(new Dimension(300, 200));

        var cpane = getContentPane();
        var gl = new GroupLayout(cpane);
        cpane.setLayout(gl);

        gl.setAutoCreateGaps(true);
        gl.setAutoCreateContainerGaps(true);

        var okButton = new JButton("OK");
        var closeButton = new JButton("Close");

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addPreferredGap(RELATED,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(okButton)
                .addComponent(closeButton)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addPreferredGap(RELATED,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(gl.createParallelGroup()
                        .addComponent(okButton)
                        .addComponent(closeButton))
        );

        gl.linkSize(SwingConstants.HORIZONTAL, okButton, closeButton);

        pack();

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

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new GroupLayoutCornerButtonsEx();
            ex.setVisible(true);
        });
    }
}

该示例使用 GroupLayout 管理器创建角落按钮。

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addPreferredGap(RELATED,
                GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(okButton)
        .addComponent(closeButton)
);

在水平布局中,我们添加一个可伸缩的间隙和两个单独的组件。可伸缩的间隙将两个按钮推到右侧。间隙是通过 addPreferredGap() 方法调用创建的。它的参数是间隙的类型、间隙的首选和最大大小。最大值和首选值之间的差异是间隙的可伸缩性。当这两个值相同时,间隙具有固定大小。

gl.setVerticalGroup(gl.createSequentialGroup()
        .addPreferredGap(RELATED,
                GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addGroup(gl.createParallelGroup()
                .addComponent(okButton)
                .addComponent(closeButton))
);

在垂直布局中,我们添加一个可伸缩的间隙和一个由两个组件组成的并行组。同样,间隙将按钮组推到底部。

gl.linkSize(SwingConstants.HORIZONTAL, okButton, closeButton);

linkSize() 方法使两个按钮大小相同。我们只需要设置它们的宽度,因为它们的高度默认已经是相同的。

GroupLayout corner buttons
图:GroupLayout 角落按钮

GroupLayout 密码示例

以下布局可以在基于表单的应用程序中找到,这些应用程序由标签和文本字段组成。

com/zetcode/GroupLayoutPasswordEx.java
package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.Alignment.BASELINE;
import static javax.swing.GroupLayout.Alignment.TRAILING;

public class GroupLayoutPasswordEx extends JFrame {

    public GroupLayoutPasswordEx() {

        initUI();
    }

    private void initUI() {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        var serviceLbl = new JLabel("Service:");
        var userNameLbl = new JLabel("User name:");
        var passwordLbl = new JLabel("Password:");

        var field1 = new JTextField(10);
        var field2 = new JTextField(10);
        var field3 = new JTextField(10);

        gl.setAutoCreateGaps(true);
        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup(TRAILING)
                        .addComponent(serviceLbl)
                        .addComponent(userNameLbl)
                        .addComponent(passwordLbl))
                .addGroup(gl.createParallelGroup()
                        .addComponent(field1)
                        .addComponent(field2)
                        .addComponent(field3))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(serviceLbl)
                        .addComponent(field1))
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(userNameLbl)
                        .addComponent(field2))
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(passwordLbl)
                        .addComponent(field3))
        );

        pack();

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

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new GroupLayoutPasswordEx();
            ex.setVisible(true);
        });
    }
}

要求是:标签必须在水平方向上右对齐,并且它们必须在垂直方向上与它们对应的文本字段对齐到它们的基线。

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addGroup(gl.createParallelGroup(TRAILING)
                .addComponent(serviceLbl)
                .addComponent(userNameLbl)
                .addComponent(passwordLbl))
        .addGroup(gl.createParallelGroup()
                .addComponent(field1)
                .addComponent(field2)
                .addComponent(field3))
);

在水平方向上,布局由两个并行组组成,打包在一个顺序组中。标签和字段分别放入它们的并行组中。标签的并行组具有 GroupLayout.Alignment.TRAILING 对齐方式,这使得标签右对齐。

gl.setVerticalGroup(gl.createSequentialGroup()
        .addGroup(gl.createParallelGroup(BASELINE)
                .addComponent(serviceLbl)
                .addComponent(field1))
        .addGroup(gl.createParallelGroup(BASELINE)
                .addComponent(userNameLbl)
                .addComponent(field2))
        .addGroup(gl.createParallelGroup(BASELINE)
                .addComponent(passwordLbl)
                .addComponent(field3))
);

在垂直布局中,我们确保标签与其文本字段对齐到它们的基线。为此,我们将标签及其对应的字段分组到具有 GroupLayout.Alignment.BASELINE 对齐方式的并行组中。

GroupLayout password example
图:GroupLayout 密码示例

在本章中,我们使用内置的 GroupLayout 管理器创建了布局。