Search code examples
javaswingalignmentvertical-alignmentgridbaglayout

Vertically center GridBagLayout like BoxLayout


I am trying to center components using a GridBagLayout in the same manner that a Box centers components when you use Box.createVerticalGlue(). I initially did use a vertical Box:

Box box = Box.createVerticalBox();
box.add(Box.createVerticalGlue());
box.add(add);
box.add(remove);
box.add(edit);
box.add(Box.createVerticalGlue());

JPanel internalPanel = new JPanel(new BorderLayout());
internalPanel.add(keywordsScrollPane, BorderLayout.CENTER);
internalPanel.add(box, BorderLayout.EAST);

But as you can see, it looks sloppy because my buttons are different sizes:

Sloppy Buttons

I decided to switch to GridBagLayout so I can utilize GridBagConstraints.fill. This approach fixes my button width issue, but I cannot figure out how to vertically center the buttons. I changed the grid size and placed the buttons in the middle three rows, but the buttons were still appearing at the top of the panel. I tried making use of GridBagConstraints.anchor and GridBagConstraints.weighty as well. The latter almost worked, but there are very large margins between the buttons:

Large Margins

I am looking for the buttons to be grouped together as they were in my Box approach. How can I achieve this with a GridBadLayout?

I am using a class I created called ConstraintsBuilder which works exactly as you would expect. It's for creating GridBagContraints with nice one-liners. Here is all the (relevant) code for your viewing pleasure:

public class KeywordsDialog extends JDialog implements ActionListener, ListSelectionListener {

    private JList<String> keywords;
    private JScrollPane keywordsScrollPane;
    private JButton add;
    private JButton remove;
    private JButton edit;

    private Set<String> keywordsList;

    public KeywordsDialog(Window parent, Collection<String> keywordsList) {
        super(parent);

        this.keywordsList = keywordsList == null ? new HashSet<String>() : new HashSet<String>(keywordsList);
        if (keywordsList != null && !keywordsList.isEmpty()) {
            this.keywords = new JList<String>(toListModel(keywordsList));
        } else {
            this.keywords = new JList<String>(new DefaultListModel<String>());
        }
        this.keywordsScrollPane = new JScrollPane(keywords);

        this.add = new JButton("Add");
        this.remove = new JButton("Remove");
        this.edit = new JButton("Edit");

        this.edit.setEnabled(false);
        this.add.setEnabled(false);

        ConstraintsBuilder builder = LayoutUtils.gridBagConstraintsBuilder();
        JPanel internalPanel = new JPanel(new GridBagLayout());
        internalPanel.add(this.keywordsScrollPane, builder.gridX(0).gridY(0).gridHeight(3).margins(0, 0, 0, 5)
                .fill(GridBagConstraints.BOTH).weightX(1D).weightY(1D).build());
        internalPanel.add(this.add,
                builder.reset().gridX(1).gridY(0).fill(GridBagConstraints.HORIZONTAL).weightX(1D).weightY(1D).build());
        internalPanel.add(this.remove,
                builder.reset().gridX(1).gridY(1).fill(GridBagConstraints.HORIZONTAL).weightX(1D).weightY(1D).build());
        internalPanel.add(this.edit,
                builder.reset().gridX(1).gridY(2).fill(GridBagConstraints.HORIZONTAL).weightX(1D).weightY(1D).build());

        this.keywords.setBorder(BorderFactory.createTitledBorder("Keywords"));
        internalPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        this.setLayout(new BorderLayout());
        this.add(internalPanel, BorderLayout.CENTER);

        Dimension screen = GuiHelper.getScreenSize(parent);
        this.setSize((int) (screen.getWidth() / 4), (int) (screen.getHeight() / 3));
        this.setLocationRelativeTo(parent);
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    }
    // ...
}

Solution

  • I would make the GUI simpler. Put the three buttons into a JPanel that uses a GridLayout, one declared to use 1 column and variable number of rows, one with a desired spacing between buttons, here, 5 pixels: JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 5, 5)); and then put that JPanel into the center of a another JPanel, and GridBagLayout without constraints works well for this:

    JPanel sidePanel = new JPanel(new GridBagLayout());
    sidePanel.add(buttonPanel);
    

    and put that JPanel into the right side of a border layout using JPanel. For example:

    enter image description here

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class FooSwing01 extends JPanel {
    
        public FooSwing01() {
            JTextArea textArea = new JTextArea(20, 50);
            JScrollPane scrollPane = new JScrollPane(textArea);
            
            JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 5, 5));
            int maxButtons = 3;
            for (int i = 0; i < maxButtons; i++) {
                buttonPanel.add(new JButton("Button " + (i + 1)));
            }
            
            JPanel sidePanel = new JPanel(new GridBagLayout());
            sidePanel.add(buttonPanel);
            
            setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            setLayout(new BorderLayout(5, 5));
            add(scrollPane);
            add(sidePanel, BorderLayout.LINE_END);
        }
        
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                JFrame frame = new JFrame("GUI");
                frame.add(new FooSwing01());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            });
        }
    }