Java Swing:当 GridBagLayout 列设置为增长时,如何使 JList 正确调整大小,忽略 JList 的项目宽度?

Java Swing: How to make JLists resize properly when GridBagLayout column is set to grow, ignoring the JList's item widths?

我得到了以下 GridBagLayout:

JList 的第一列和最后一列设置为 grow: 1。我不希望这些 JList 始终具有相同的宽度,无论包含的项有多宽。我怎样才能做到这一点?

我已经用 list.setFixedCellWidth(100) 试过了,但是周围的 JScrollPane 不再工作了。

您需要在 JLists 上设置原型单元格值

jList.setPrototypeCellValue("asdf");

在两个 JList 上设置相同的文本。然后 JList 将 "pretend" 所有项目都与提供的文本一样宽。此宽度将用于确定是否需要滚动条,因此您应该找到要添加到 JList 的最长项目并将原型设置为该项目。

使用 setFixedCellWidth(int) 设置固定单元格宽度也可以,但您必须自己计算最宽文本的宽度。

所以,你的问题是多层次的。问题是,JList 提供了两个值,用于确定 JList

的布局

第一个是getPreferredSizeJScrollPane 将使用它来确定何时需要显示滚动条。

第二个是getPreferredScrollableViewportSize。这为 JScrollPane 提供了一个提示,这将影响它的 preferredSize,它为布局管理器提供了一个关于如何布局的提示。

一种可能的解决方案是提供有关 getPreferredScrollableViewportSize 的更多信息,以便两个 JList 生成相同的值。然后,通过一些巧妙的布局约束,您可以使两个列表保持相同的大小。

因此,我们从 JList 开始扩展并提供一种查看 preferredScrollableViewportSize 的方法。

public class FixedSizeList<E> extends JList<E> {
    
    private Dimension preferredScrollableViewportSize;

    public FixedSizeList(ListModel<E> dataModel, Dimension preferredScrollableViewportSize) {
        super(dataModel);
        this.preferredScrollableViewportSize = preferredScrollableViewportSize;
    }

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        if (preferredScrollableViewportSize != null) {
            return preferredScrollableViewportSize;
        }
        return super.getPreferredScrollableViewportSize();
    }
    
}

现在,就我个人而言,我可能很想使用 FontMetricsJLists Font 来确定应该如何最好地使用这些值,所以你可以提供 columnrow 值(或使用 visibleRowCount 属性)。这意味着这些值是“字符”而不是像素,但这只是我。

接下来,我们需要限制布局,使两个列表不会占用可用空间的 50% space。

setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 0.5;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;

JList shortList = new FixedSizeList(new ShortModel(), new Dimension(150, 300));
JList longList = new FixedSizeList(new LongModel(), new Dimension(150, 300));

add(new JScrollPane(shortList), gbc);
gbc.gridx += 2;
add(new JScrollPane(longList), gbc);

gbc.weightx = 0;
gbc.gridx = 1;

JPanel buttons = new JPanel(new GridBagLayout());
gbc = new GridBagConstraints();
gbc.fill = gbc.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
for (int index = 0; index < 5; index++) {
    buttons.add(new JButton(Integer.toString(index)), gbc);
}
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
add(buttons, gbc);

这是一种有趣的说法,即“两个列表都将填充剩余的可用列表 space”。由于我们额外限制了 scrollableViewportSize,这将为两个列表提供相等的间距

这是一些“繁重”的操作(恕我直言),但它胜过每次向 this 添加新元素时循环遍历两个模型的所有元素

可运行示例...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.Random;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.weightx = 1.0;
            gbc.weighty = 1.0;
            gbc.fill = GridBagConstraints.BOTH;

            JList shortList = new FixedSizeList(new ShortModel(), new Dimension(150, 300));
            JList longList = new FixedSizeList(new LongModel(), new Dimension(150, 300));

            add(new JScrollPane(shortList), gbc);
            gbc.gridx += 2;
            add(new JScrollPane(longList), gbc);

            gbc.weightx = 0;
            gbc.gridx = 1;

            JPanel buttons = new JPanel(new GridBagLayout());
            gbc = new GridBagConstraints();
            gbc.fill = gbc.HORIZONTAL;
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            for (int index = 0; index < 5; index++) {
                buttons.add(new JButton(Integer.toString(index)), gbc);
            }
            gbc = new GridBagConstraints();
            gbc.gridx = 1;
            gbc.gridy = 0;
            gbc.weighty = 1.0;
            gbc.fill = GridBagConstraints.BOTH;
            add(buttons, gbc);
        }

    }

    public class FixedSizeList<E> extends JList<E> {

        private Dimension preferredScrollableViewportSize;

        public FixedSizeList(ListModel<E> dataModel, Dimension preferredScrollableViewportSize) {
            super(dataModel);
            this.preferredScrollableViewportSize = preferredScrollableViewportSize;
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            if (preferredScrollableViewportSize != null) {
                return preferredScrollableViewportSize;
            }
            return super.getPreferredScrollableViewportSize();
        }

    }

    public class SizableModel extends DefaultListModel<String> {

        public String makeText(int length) {
            StringBuilder sb = new StringBuilder(length);
            for (int index = 0; index < length; index++) {
                String text = Integer.toString(index);
                sb.append(text.substring(text.length() - 1));
            }
            return sb.toString();
        }

    }

    public class ShortModel extends SizableModel {

        public ShortModel() {
            Random rnd = new Random();
            for (int index = 0; index < 5; index++) {
                addElement(makeText(Math.max(1, rnd.nextInt(5))));
            }
        }

    }

    public class LongModel extends SizableModel {

        public LongModel() {
            Random rnd = new Random();
            for (int index = 0; index < 10; index++) {
                addElement(makeText(Math.max(5, rnd.nextInt(15))));
            }
        }

    }
}

GridBagLayout 无法做到这一点。然而,SpringLayout是。

SpringLayout 使用起来很麻烦,所以通常不是一个好的选择,但它可以做一些其他布局做不到的事情。在这种情况下,它使一个组件的尺寸约束依赖于另一个组件的尺寸约束的能力是适用的:

JPanel buttonsPanel = new JPanel(new GridLayout(0, 1, 6, 6));
buttonsPanel.add(new JButton("\u2190"));
buttonsPanel.add(new JButton("\u21c7"));
buttonsPanel.add(new JButton("\u21c9"));
buttonsPanel.add(new JButton("\u2192"));

JPanel centerPanel = new JPanel(new GridBagLayout());
centerPanel.add(buttonsPanel, new GridBagConstraints());
centerPanel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 12));

centerPanel.setMaximumSize(new Dimension(
    centerPanel.getPreferredSize().width,
    centerPanel.getMaximumSize().height));

JList<String> leftList = new JList<>(new String[] {
    "Erwartete Gäste pro Segmentcode",
    "Restaurant Auslastungsvorschau",
    "Restaurant Tagesauslastung",
});

JList<String> rightList = new JList<>(new String[] {
    "Info",
    "Ressourcenauslastung",
    "Provisionsanzeige",
    "Umsatzstatistik",
    "Top Anzahl/Umsatz",
    "Mitgliedsübersicht",
    "Gastherkunft",
    "Capture rate",
});

JScrollPane leftPane = new JScrollPane(leftList);
JScrollPane rightPane = new JScrollPane(rightList);

SpringLayout.Constraints leftConstraints =
    new SpringLayout.Constraints(leftPane);

SpringLayout.Constraints rightConstraints =
    new SpringLayout.Constraints(rightPane);

SpringLayout.Constraints centerConstraints =
    new SpringLayout.Constraints(centerPanel);

Spring width = Spring.max(
    leftConstraints.getWidth(),
    rightConstraints.getWidth());

Spring height = Spring.max(
    leftConstraints.getHeight(),
    rightConstraints.getHeight());

leftConstraints.setWidth(width);
leftConstraints.setHeight(height);
rightConstraints.setWidth(width);
rightConstraints.setHeight(height);
centerConstraints.setHeight(height);

centerConstraints.setConstraint(SpringLayout.WEST,
    leftConstraints.getConstraint(SpringLayout.EAST));

rightConstraints.setConstraint(SpringLayout.WEST,
    centerConstraints.getConstraint(SpringLayout.EAST));

SpringLayout layout = new SpringLayout();
JPanel listsPanel = new JPanel(layout);

listsPanel.add(leftPane, leftConstraints);
listsPanel.add(centerPanel, centerConstraints);
listsPanel.add(rightPane, rightConstraints);

// Set width of container
layout.putConstraint(
    SpringLayout.EAST, listsPanel, 0,
    SpringLayout.EAST, rightPane);

// Set height of container
layout.putConstraint(
    SpringLayout.SOUTH, listsPanel, 0,
    SpringLayout.SOUTH, rightPane);