创建 Inspector-like 东西时 JScrollPane 和 GridBagLayout 问题

JScrollPane and GridBagLayout problems while creating Inspector-like thing

我正在为我的程序创建检查面板之类的东西,因此我可以在 运行 时轻松更改一些变量。

当你运行下面的代码时,你应该看到这样的东西: inspector screenshot

有几件事困扰着我,我仍然无法使它们正常工作。

  1. 项目与中心垂直对齐...我已经尝试将锚点设置为北方,但它保持不变。在下面的示例代码中,有一行被注释掉可以解决这个问题:inspector.finish() 但这个解决方案对我来说似乎有点老套。它的工作原理是将空的 JPanel 添加为面板上的最后一项,充当某种垂直胶水以扩展下部区域并将组件向上推。我不喜欢这个,因为有了这个解决方案,我以后无法再在 运行 时间向检查器添加更多项目。
  2. 如果您向检查器添加更多项目(您可以通过更改 n 变量来为检查器填充一些测试数据),滚动条将不会显示,并且所有较低的项目都在屏幕外,即使它都被包裹在 JScrollPane 中......我已经尝试了几个 hack 来解决这个问题,但是 none 它们可以正常工作。其中之一是将 JPanel 的 preferredSize 设置为一些 hard-coded 尺寸,但我不喜欢这样,因为我不知道面板的确切尺寸。
  3. 当您按下一些微调按钮然后单击组合框(标题为 "choose me")时,选项将隐藏在下面的按钮后面。这看起来像是某种 z-ordering 问题。也许这是 swing 中的错误,不知道。
  4. 当您将 window 垂直调整为更小尺寸时,顶部的项目将开始缩小,而不是保持相同尺寸。是否有任何选项可以始终将它们设置为恒定高度?

也许我使用了错误的布局管理器,但我不知道该选择哪个。我在考虑简单的网格布局,但这不允许我做一些事情,比如让一些行包含一个项目,而另一些行包含多个项目,这样它们将始终使用行的整个宽度 "capacity"。

示例代码:

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

public class Test {

    // Setup test scenario
    public Test() {

        // Create window
        JFrame f = new JFrame();
        f.setLayout(new BorderLayout());
        f.setSize(800, 600);
        f.setTitle("Inspector");
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // Create panel which will be used for the inspector
        JPanel p = new JPanel();
        p.setPreferredSize(new Dimension(200, 0));

        // Encapsulate it to the scroll panel so it can grow vertically
        JScrollPane sp = new JScrollPane();
        sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        sp.setViewportView(p);

        // Create the inspector inside panel p
        Inspector inspector = new Inspector(p);

        // Fill the inspector with some test data
        int n = 3;
        for (int i = 0; i < n; ++i) {
            inspector.addTitle("Title");
            inspector.addButton("push me");
            inspector.addCheckBox("check me");
            inspector.addSpinner("Spin me");
            inspector.addTextField("Edit me", "here");
            inspector.addComboBox("Choose one", new String[]{"A", "B", "C"});
            inspector.addSeparator();
        }
        //inspector.finish();

        // The inspector will be on the right side of the window
        f.getContentPane().add(sp, BorderLayout.LINE_END);

        // Show the window
        f.setVisible(true);
    }

    // Main method
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Test();
            }
        });
    }

    // Inspector class itself
    private class Inspector {

        private final JPanel panel;
        private final GridBagConstraints constraints;
        private int itemsCount = 0;

        public Inspector(JPanel p) {
            panel = p;
            panel.setLayout(new GridBagLayout());

            constraints = new GridBagConstraints();
            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.weightx = 0.5;
        }

        // Adds component which will span across whole row
        private void addComponent(Component component) {
            constraints.gridx = 0;
            constraints.gridwidth = 2;
            constraints.gridy = itemsCount;

            panel.add(component, constraints);

            itemsCount++;
        }

        // Adds descriptive label on the left and component on the right side of the row
        private void addNamedComponent(String description, Component component) {
            constraints.gridx = 0;
            constraints.gridy = itemsCount;
            constraints.gridwidth = 1;
            panel.add(new JLabel(description), constraints);

            constraints.gridx = 1;
            constraints.gridy = itemsCount;
            constraints.gridwidth = 1;
            panel.add(component, constraints);

            itemsCount++;
        }

        public void addSeparator() {
            // (little hacky)
            addComponent(new JPanel());
        }

        public void addTitle(String title) {
            addComponent(new JLabel(title));
        }

        public void addButton(String actionName) {
            addComponent(new Button(actionName));
        }

        public void addCheckBox(String description) {
            addNamedComponent(description, new JCheckBox());
        }

        public void addSpinner(String description) {
            addNamedComponent(description, new JSpinner());
        }

        public void addTextField(String description, String text) {
            addNamedComponent(description, new JTextField(text));
        }

        public void addComboBox(String description, String[] options) {
            JComboBox<String> comboBox = new JComboBox<>();
            ComboBoxModel<String> model = new DefaultComboBoxModel<>(options);
            comboBox.setModel(model);

            addNamedComponent(description, comboBox);
        }

        public void finish() {
            constraints.gridx = 0;
            constraints.gridy = itemsCount;
            constraints.gridwidth = 2;
            constraints.weighty = 1.0;

            panel.add(new JPanel(), constraints);
        }
    }
}

您无法向上或向下滚动的原因是您将 sp 的首选大小设置为 200x0。您需要删除此行。

p.setPreferredSize(new Dimension(200, 0));

至于一切都居中而不是顶部的问题。我更愿意将 p 保留为默认值 FlowLayout,并为每个 "section" 提供自己的面板,并使该面板成为 GridBagLayout。通过这样做,您可能不再需要 addSeparator()

public class Test {

    // Setup test scenario
    public Test() {

        // Create window
        JFrame f = new JFrame();
        f.setLayout(new BorderLayout());
        f.setSize(800, 600);
        f.setTitle("Inspector");
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // Create panel which will be used for the inspector
        JPanel p = new JPanel();

        // Encapsulate it to the scroll panel so it can grow vertically
        JScrollPane sp = new JScrollPane();
        sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        sp.setViewportView(p);

        // Create the inspector inside panel p
        Inspector inspector = new Inspector(p);

        // Fill the inspector with some test data
        int n = 2;
        for (int i = 0; i < n; ++i) {
            inspector.addTitle("Title");
            inspector.addButton("push me");
            inspector.addCheckBox("check me");
            inspector.addSpinner("Spin me");
            inspector.addTextField("Edit me", "here");
            inspector.addComboBox("Choose one", new String[]{"A", "B", "C"});
        }

        // The inspector will be on the right side of the window
        f.getContentPane().add(sp, BorderLayout.LINE_END);

        // Show the window
        f.setVisible(true);
    }

    // Main method
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Test();
            }
        });
    }

    // Inspector class itself
    private class Inspector {

        private final JPanel panel;
        private final GridBagConstraints constraints;
        private int itemsCount = 0;

        public Inspector(JPanel p) {
            panel = new JPanel();
            p.add(panel);
            panel.setLayout(new GridBagLayout());

            constraints = new GridBagConstraints();
            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.weightx = 1.0;
        }

        // Adds component which will span across whole row
        private void addComponent(Component component) {
            constraints.gridx = 0;
            constraints.gridwidth = 2;
            constraints.gridy = itemsCount;

            panel.add(component, constraints);

            itemsCount++;
        }

        // Adds descriptive label on the left and component on the right side of the row
        private void addNamedComponent(String description, Component component) {
            constraints.gridx = 0;
            constraints.gridy = itemsCount;
            constraints.gridwidth = 1;
            panel.add(new JLabel(description), constraints);

            constraints.gridx = 1;
            constraints.gridy = itemsCount;
            constraints.gridwidth = 1;
            panel.add(component, constraints);

            itemsCount++;
        }

        public void addSeparator() {
            // (little hacky)
            addComponent(new JPanel());
        }

        public void addTitle(String title) {
            addComponent(new JLabel(title));
        }

        public void addButton(String actionName) {
            addComponent(new Button(actionName));
        }

        public void addCheckBox(String description) {
            addNamedComponent(description, new JCheckBox());
        }

        public void addSpinner(String description) {
            addNamedComponent(description, new JSpinner());
        }

        public void addTextField(String description, String text) {
            addNamedComponent(description, new JTextField(text));
        }

        public void addComboBox(String description, String[] options) {
            JComboBox<String> comboBox = new JComboBox<>();
            ComboBoxModel<String> model = new DefaultComboBoxModel<>(options);
            comboBox.setModel(model);

            addNamedComponent(description, comboBox);
        }

        public void finish() {
            constraints.gridx = 0;
            constraints.gridy = itemsCount;
            constraints.gridwidth = 2;
            constraints.weighty = 1.0;

            panel.add(new JPanel(), constraints);
        }
    }
}