【问题标题】:MigLayout span components overflowing from cellMigLayout 跨度组件从单元格溢出
【发布时间】:2014-05-13 01:54:46
【问题描述】:

我正在尝试在 MigLayout 中创建类似于以下内容的布局:

基本上 3 列:一个按钮、一个标签和一个其他组件,底部有一个列表(跨越 2 列)和一个文本区域(应该与第三列中的其他组件对齐)。诀窍是按钮列跨越除最后两行之外的所有行,并将其分成 8 个(每个按钮一个)。但是,我最终将按钮重叠在列表中,并且通过调试,您可以看到这些按钮实际上是从它们的单元格中溢出的。当我将按钮放在另一个面板中然后将其添加到主面板时,这种情况仍然会发生。

在调整窗口大小时,添加胶水(或其他一些实际上不可见的组件)会导致按钮和底部组件之间出现间隙(我希望底部组件占用所有额外空间)。

有没有办法将底部组件推到按钮下方,以便它们从调整大小中获得额外空间?

会发布屏幕截图,但我是第一次发帖,所以我没有代表谢谢 mKorbel!)

代码:

import net.miginfocom.swing.MigLayout;

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

public class MigLayoutTest extends JPanel
{
    private MigLayoutTest()
    {
        JFrame frame = new JFrame("MigLayout Test");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setContentPane(new JScrollPane(getPage()));
        frame.getContentPane().setMinimumSize(new Dimension(650, 336));
        frame.getContentPane().setPreferredSize(new Dimension(890, 562));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private JPanel getPage()
    {
        JPanel panel = new JPanel(new MigLayout("fill, wrap 3, debug", "[][][grow, fill]"));
        panel.setBorder(new EmptyBorder(10, 10, 10, 10));

        // To add buttons directly to panel uncomment the commented out lines below and comment out each line that references listButtonPanel
        JPanel listButtonPanel = new JPanel(new MigLayout("ins 0, wrap 1, aligny top"));
        Dimension btnSize = new Dimension(105, 25);
        JButton addBtn = new JButton("Add");
        addBtn.setPreferredSize(btnSize);
        listButtonPanel.add(addBtn);
        // panel.add(addBtn, "spany 4, split 8, flowy");

        JButton removeBtn = new JButton("Remove");
        removeBtn.setPreferredSize(btnSize);
        listButtonPanel.add(removeBtn);
        // panel.add(removeBtn);

        JButton copyBtn = new JButton("Copy");
        copyBtn.setPreferredSize(btnSize);
        listButtonPanel.add(copyBtn);
        // panel.add(copyBtn)

        panel.add(listButtonPanel, "spany 2, aligny top, hmax 100%");

        JTextField txtField = new JTextField();
        JLabel label = new JLabel("Property 1");
        label.setLabelFor(txtField);
        panel.add(label, "alignx right");
        panel.add(txtField);

        JComboBox comboBox = new JComboBox(new String[] {"cbx itm 1", "cbx itm 2", "cbx itm 3"});
        comboBox.setEditable(true);
        comboBox.setSelectedItem("");
        label = new JLabel("ComboBox Property");
        label.setLabelFor(comboBox);
        panel.add(label, "alignx right");
        panel.add(comboBox);

        panel.add(new JLabel("A big JList"), "spanx 2, grow");
        panel.add(new JLabel("A big JTextArea"));

        JList list = new JList(new DefaultListModel());
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setVisibleRowCount(-1);
        String[] names = new String[] {"Stuff to", "fill this", "JList..."};
        DefaultListModel model = (DefaultListModel)list.getModel();
        for (String name : names)
            model.addElement(name);
        JScrollPane scroller = new JScrollPane(list);
        scroller.setMinimumSize(new Dimension(213, 100));
        scroller.setPreferredSize(new Dimension(213, 100));
        panel.add(scroller, "spanx 2, grow, pushy");

        JTextArea textArea = new JTextArea();
        scroller = new JScrollPane(textArea);
        scroller.setPreferredSize(new Dimension(100, 100));
        panel.add(scroller, "grow, pushy");

        return panel;
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() { new MigLayoutTest(); }
        });
    }
}

【问题讨论】:

  • +1,用于图片和所有相关代码

标签: java swing layout-manager miglayout


【解决方案1】:

只要面板具有静态行数,Thomas 的答案就有效,不幸的是我们的程序没有。为了解决这个问题,我会跟踪要创建的行数,然后在最后动态生成行约束。 (这有点混乱,但没有我预期的那么糟糕。如果有办法避免行约束,我会相应地更新代码。)我还在按钮面板旁边的组件与列表和文本字段之间使用了一个虚拟行在底部以占用额外的空间,保持所有内容正确对齐而无需调整大小。

import net.miginfocom.swing.MigLayout;

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

public class MigLayoutTest extends JPanel
{
    private MigLayoutTest(boolean addExtraRow1, boolean addExtraRow2)
    {
        JFrame frame = new JFrame("MigLayout Test");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setContentPane(new JScrollPane(getPage(addExtraRow1, addExtraRow2)));
        frame.getContentPane().setMinimumSize(new Dimension(650, 336));
        frame.getContentPane().setPreferredSize(new Dimension(890, 562));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private JPanel getPage(boolean addExtraRow1, boolean addExtraRow2)
    {
        // Create buttons panel
        JPanel listButtonPanel = new JPanel(new MigLayout("ins 0, wrap 1, aligny top"));
        JButton addBtn = new JButton("Add");
        listButtonPanel.add(addBtn, "w 105px, h 25px, sg btns");

        JButton removeBtn = new JButton("Remove");
        listButtonPanel.add(removeBtn, "sg btns");

        JButton copyBtn = new JButton("Copy");
        listButtonPanel.add(copyBtn, "sg btns");

        // Create other components
        int rowCount = 1; // make a dummy row at the bottom to push all the components beside the button panel up

        JTextField txtField = new JTextField();
        rowCount++;

        JComboBox comboBox = new JComboBox(new String[] {"cbx itm 1", "cbx itm 2", "cbx itm 3"});
        comboBox.setEditable(true);
        comboBox.setSelectedItem("");
        rowCount++;

        JCheckBox checkBox = null;
        if (addExtraRow1)
        {
            checkBox = new JCheckBox();
            rowCount++;

        }

        JTextField optTxtField = null;
        if (addExtraRow2)
        {
            optTxtField = new JTextField();
            rowCount++;
        }

        rowCount++;

        JList list = new JList(new DefaultListModel());
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setVisibleRowCount(-1);
        String[] names = new String[] {"Stuff to", "fill this", "JList..."};
        DefaultListModel model = (DefaultListModel)list.getModel();
        for (String name : names)
            model.addElement(name);

        JTextArea textArea = new JTextArea();

        // Generate row constraints
        StringBuilder rowConstraints = new StringBuilder();
        for (int i=0; i<rowCount; i++)
            rowConstraints.append("[]");
        rowConstraints.append("[grow, fill]");

        // Create main panel and add components
        JPanel panel = new JPanel(new MigLayout("fill, wrap 3, debug", "[][][grow, fill, align left]", rowConstraints.toString()));
        panel.setBorder(new EmptyBorder(10, 10, 10, 10));

        panel.add(listButtonPanel, "spany " + --rowCount + ", aligny top, hmax 100%"); // decrement rowCount because the buttons should be above the bottom components' labels

        addComponent(panel, new JLabel("Property 1"), txtField);
        addComponent(panel, new JLabel("ComboBox Property"), comboBox);
        if (checkBox != null)
            addComponent(panel, new JLabel("Extra comp 1"), checkBox);
        if (optTxtField != null)
            addComponent(panel, new JLabel("Extra comp 2"), optTxtField);

        panel.add(new JLabel("A big JList"), "skip 3, spanx 2, grow"); // skip the dummy row before adding this
        panel.add(new JLabel("A big JTextArea"));

        panel.add(new JScrollPane(list), "hmin 100px, spanx 2, grow");
        panel.add(new JScrollPane(textArea), "hmin 100px, grow");

        return panel;
    }

    private void addComponent(JPanel panel, JLabel label, Component component)
    {
        label.setLabelFor(component);
        panel.add(label, "align right");
        panel.add(component);
    }

    public static void main(final String[] args)
    {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() { new MigLayoutTest(args.length > 1, args.length == 2); }
        });
    }
}

【讨论】:

    【解决方案2】:

    我认为“拆分和跨单元按钮面板”与 push 相结合会使事情变得混乱。我很抱歉,但我不知道为什么。一些可选提示:

    • 我喜欢将所有组件的声明、初始化和添加分开。我认为它更具可读性和更易于维护。
    • 避免使用硬编码的 setSize 函数。我不记得我曾经得到过我想要的东西。我喜欢 MigLayout 的 sizegroup 约束。
    • 不要将变量用于两个对象实例(例如滚动条)。使用对象引用经常会导致错误。

    这是另一种可能的解决方案(我希望这仍能满足您的要求):

        private JPanel getPage() {
        JPanel panel = new JPanel(new MigLayout("wrap 3, debug", "[][][grow, fill]", "[][][][grow, fill]"));
        panel.setBorder(new EmptyBorder(10, 10, 10, 10));
    
        JButton addBtn, removeBtn, copyBtn;
        JTextField txtField;
        JTextArea textArea;
        JComboBox<String> comboBox;
        JList<String> list;
        JLabel property, comboboxLabel, listLabel, textAreaLabel;
        JScrollPane listScroller, textFieldScroller;
    
        addBtn = new JButton("Add");
        removeBtn = new JButton("Remove");
        copyBtn = new JButton("Copy");
        txtField = new JTextField();
        textArea = new JTextArea();
        property = new JLabel("Property 1");
        listLabel = new JLabel("A big JList");
        textAreaLabel = new JLabel("A big JTextArea");
        comboboxLabel = new JLabel("ComboBox Property");
    
    
        comboBox = new JComboBox<String>(new String[] { "cbx itm 1", "cbx itm 2", "cbx itm 3" });
        comboBox.setEditable(true);
        comboBox.setSelectedItem("");
    
        list = new JList<String>();
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setVisibleRowCount(-1);
    
        String[] names = new String[] { "Stuff to", "fill this", "JList..." };
        DefaultListModel<String> model = new DefaultListModel<String>();
        for (String name : names) {
            model.addElement(name);
        }
        list.setModel(model);
    
    
        listScroller = new JScrollPane(list);
        textFieldScroller = new JScrollPane(textArea);
    
        property.setLabelFor(txtField);
        comboboxLabel.setLabelFor(comboBox);
        textAreaLabel.setLabelFor(textArea);
        listLabel.setLabelFor(list);
    
    
        panel.add(addBtn, "split 3, flowy, spany 2, sizegroup buttons");
        panel.add(removeBtn, "sizegroup buttons");
        panel.add(copyBtn, "sizegroup buttons");
    
        panel.add(property, "al right top, sgy components");
        panel.add(txtField, "al right top, sgy components");
        panel.add(comboboxLabel, "al right top, sgy components");
        panel.add(comboBox, "al right top, sgy components");
    
        panel.add(listLabel, "spanx 2, sgy components");
        panel.add(textAreaLabel, "sgy components");
        panel.add(listScroller, "spanx 2, grow");
        panel.add(textFieldScroller, "");
    
        return panel;
    }
    

    【讨论】:

    • 非常感谢。我认为 split 不一定与它有任何关系,因为当我将按钮放在他们自己的面板中并使用 spany 时,我得到了相同的结果(请参阅 getPage 方法中的顶部评论)。还有没有可能避免行约束?我们程序中的面板具有动态数量的组件(文本字段和组合框是这些组件的占位符),我们可以有超过 15 行,这使得行约束有点笨拙。
    • 当然:将行和/或列约束留空,并在将组件添加到面板时使用组件约束。不过,要编写更多代码。
    • 那么我可以在组件约束中使用什么来替换最后一行约束中的填充?我正在使用 pushy,但这就是导致问题的原因......
    • 哦,现在这是个问题。您可以将顶级组件包装在一个新面板中,然后在您的 mainPanel 中有 3 行(组件、标题、列表+文本区域),但是您的组件将不会与文本区域和列表对齐。否则,您可以摆脱跨越三行的按钮并使用 pushy,因为 pushy/spany 不能组合使用(只是尝试过)。您能否在帖子中添加一个示例,其中插入了一两个动态行?
    • 我已经发布了一个示例,说明我最终做了什么(带有几个动态添加的组件)作为答案。你能看看我是否可以避免生成行约束吗?感谢您的所有帮助。