Question

I'm trying to create a layout similar to the following in MigLayout:

Basically 3 columns: one of buttons, one of labels, and one of other components with a list (spanning 2 columns) and a text area (which should line up with the other components in the third column) at the bottom. The trick is the buttons column spans all the rows apart from the last two and splits that into 8 (one for each button). However I end up with the buttons overlapping the list and with debug on you can see that the buttons are actually spilling out of their cell. This still happens when I put the buttons in another panel and then add that to the main panel.

Adding in glue (or some other practically invisible component) results in a gap occurring between the buttons and the bottom components when the window is resized (I want the bottom components to take up all the extra space).

Is there some way to push the bottom components to below the buttons so that they get any extra space from a resize?

(Would have posted screenshots but my first post so I don't have the rep Thanks mKorbel!)

enter image description here

Code:

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(); }
        });
    }
}
Was it helpful?

Solution

Thomas' answer works as long as the panel has a static number of rows, which unfortunately our program doesn't. To get around this I keep track of how many rows I'm going to create and then dynamically generate the row constraints at the end. (This is a bit messy but not as bad as I expected. If there's a way to avoid the row constraints I'll update the code accordingly.) I also used a dummy row between the components beside the button panel and the list and textfield at the bottom to take up the extra space keeping everything aligned properly without resizing.

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); }
        });
    }
}

OTHER TIPS

I think the "split and span cell button panel" in combination with push messes the thing up. I'm sorry but I don't know why. Some optional tips:

  • I like to seperate declaration, initialisation and adding of all of the components. I think it is more readable and easier to maintain.
  • Avoid hardcoded setSize functions. I don't remember that I ever got what I wanted. I like the sizegroup contraint with MigLayout.
  • Do not use a variable for two object instances (e.g. scroller). It often leads to mistakes using the object references.

Here is another possible solution (I hope this still satisfies your requirements):

    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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top