javaswinggridbaglayout

Java Swing GridBagLayout not as expected


Some how I can't get gridwidth to work in GridBagLayout in my simple SwingApp.

I have this code in Java Swing:

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;

class GridBagLayt extends JFrame {
 
    GridBagLayt()
    {
        JButton jb1 = new JButton("1");
        JButton jb2 = new JButton("2");
        JButton jb3 = new JButton("3");
        JButton jb4 = new JButton("4");
        JButton jb5 = new JButton("5");
        
        GridBagLayout lay = new GridBagLayout();
        GridBagConstraints cons = new GridBagConstraints();
        
        setLayout(lay);
        cons.fill = GridBagConstraints.BOTH;
        cons.gridheight = 1;
        cons.weightx = 1;
        cons.weighty = 1;


        cons.gridx = 0; cons.gridy = 0; 
        cons.gridwidth = 2;
        lay.setConstraints(jb1, cons);
        add(jb1);
        
        cons.gridx = 2; 
        lay.setConstraints(jb2, cons);
        add(jb2);
         
        cons.gridx = 0; cons.gridy = 1; 
        cons.gridwidth = 1;
        lay.setConstraints(jb3, cons);
        add(jb3);
        
        cons.gridx = 1;
        cons.gridwidth = 2;
        lay.setConstraints(jb4, cons);
        add(jb4);
        
        cons.gridx = 3;
        cons.gridwidth = 1;
        lay.setConstraints(jb5, cons);
        add(jb5);
        
        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(700,200);
        setVisible(true);
    }
  }

    public class SwingApp {
 
        public static void main(String[] args) {
     
            new GridBagLayt();
        }
    }

This is what I want to get: enter image description here

But instead I get this:

enter image description here

I really don't know what the problem is, the coordinate and gridwidth seems ok to me, and I expected this to work, but it's not. And if I add four more buttons between this two rows, then the third row seems OK, but I don't want that:

Example: If I add this code

cons.gridx = 0; cons.gridy = 0; 
        cons.gridwidth = 2;
        lay.setConstraints(jb1, cons);
        add(jb1);
        
        cons.gridx = 2; 
        lay.setConstraints(jb2, cons);
        add(jb2);
        
        
        cons.gridx = 0; cons.gridy = 1; 
        cons.gridwidth = 1;
        lay.setConstraints(jb3, cons);
        add(jb3);
        
        cons.gridx = 1;
        lay.setConstraints(jb4, cons);
        add(jb4);
        
        cons.gridx = 2;
        lay.setConstraints(jb5, cons);
        add(jb5);
      
        cons.gridx = 3;
        lay.setConstraints(jb6, cons);
        add(jb6);
        
        cons.gridx = 0; cons.gridy = 2; 
        lay.setConstraints(jb7, cons);
        add(jb7);
        
        
        cons.gridx = 1; cons.gridy = 2; 
        cons.gridwidth = 2;
        lay.setConstraints(jb8, cons);
        add(jb8);
        
        cons.gridx = 3; cons.gridy = 2; 
        cons.gridwidth = 1;
        lay.setConstraints(jb9, cons);
        add(jb9);

Then I got this:

enter image description here

But I don't want this second row


Solution

  • Any suitable complex UI/layout should be split into multiple components. This is commonly known as "compound layouts".

    This allows you to better focus on the individual needs of each "area" of the layout independently.

    Sometimes, you just need to fake it.

    enter image description here

    Please note, I've used labels instead of button as a demonstration only, as MacOs buttons have a lot of space around them and this better illustrates the use of the GridBagConstraints#insets

    import java.awt.Color;
    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.Insets;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.border.EmptyBorder;
    
    public class Main {
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    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.weightx = 1;
                gbc.fill = gbc.HORIZONTAL;
                gbc.insets = new Insets(4, 0, 4, 0);
                gbc.gridwidth = GridBagConstraints.REMAINDER;
    
                add(makeFirstRow(), gbc);
                add(makeSecondRow(), gbc);
                add(makeThirdRow(), gbc);
            }
    
            protected JLabel makeLabel(String text) {
                JLabel label = new JLabel(text);
                label.setHorizontalAlignment(JLabel.CENTER);
                label.setOpaque(true);
                label.setBackground(Color.RED);
                label.setBorder(new EmptyBorder(8, 32, 8, 32));
                return label;
            }
    
            protected JPanel makeFirstRow() {
                JPanel rowPane = new JPanel(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.weightx = 1;
                gbc.fill = gbc.HORIZONTAL;
                gbc.insets = new Insets(0, 4, 0, 4);
    
                rowPane.add(makeLabel("1"), gbc);
                rowPane.add(makeLabel("2"), gbc);
    
                return rowPane;
            }
    
            protected JPanel makeSecondRow() {
                JPanel rowPane = new JPanel(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.weightx = 1;
                gbc.fill = gbc.HORIZONTAL;
                gbc.insets = new Insets(0, 4, 0, 4);
    
                rowPane.add(makeLabel("3"), gbc);
                rowPane.add(makeLabel("4"), gbc);
                rowPane.add(makeLabel("5"), gbc);
    
                return rowPane;
            }
    
            protected JPanel makeThirdRow() {
                JPanel rowPane = new JPanel(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.weightx = 1;
                gbc.fill = gbc.HORIZONTAL;
                gbc.insets = new Insets(0, 4, 0, 4);
    
                rowPane.add(makeLabel("6"), gbc);
                rowPane.add(makeLabel("7"), gbc);
                rowPane.add(makeLabel("8"), gbc);
    
                return rowPane;
            }
        }
    
    }
    

    Now, I don't know about you, but I was tempted to create some kind of factory method which would create each row, based on the number of elements I needed in each row ... but that's me 😉

    but with a little bit different layout. button 4 (and 7 in your layout) should be same size as buttons 1 and 2, but in your case they are 2/3 of the size of 1 and 2, and buttons 3 and 5 should be 1/2 of 1 and 2, and here they are 2/3

    Then change the columns weightx property accordingly, for example...

    enter image description here

    protected JPanel makeSecondRow() {
        JPanel rowPane = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = gbc.HORIZONTAL;
        gbc.insets = new Insets(0, 4, 0, 4);
    
        gbc.weightx = 0.25;
        rowPane.add(makeLabel("3"), gbc);
        gbc.weightx = 0.5;
        // This is simply adding an addition padding to the
        // labels preferred size, otherwise all three buttons
        // can be layed out at there preferred size with the
        // avaliable space
        gbc.ipadx = 64;
        rowPane.add(makeLabel("4"), gbc);
        gbc.weightx = 0.25;
        gbc.ipadx = 0;
        rowPane.add(makeLabel("5"), gbc);
    
        return rowPane;
    }
    

    Now, remember, the components own preferred size plays into this. Without the additional ipadx, all three elements can be layed out their preferred size with in the available container area.

    And, no, you don't "need" to specify the weightx for each column, this is just a demonstration, but sometimes, you might find it more useful to be absolute in your desires.