javaswinglayout-managergridbaglayout

GridBagLayout anchor preciseness


Can I eliminate random red line around the container or this is a bug in JDK?
3x3 grid is added 9 components smaller in size using enchor constraint which is not working as expected.

enter image description here

Full source code to reproduce the problem

import static java.awt.Color.red;
import static java.awt.Color.white;
import static java.awt.GridBagConstraints.CENTER;
import static java.awt.GridBagConstraints.FIRST_LINE_END;
import static java.awt.GridBagConstraints.FIRST_LINE_START;
import static java.awt.GridBagConstraints.LAST_LINE_END;
import static java.awt.GridBagConstraints.LAST_LINE_START;
import static java.awt.GridBagConstraints.LINE_END;
import static java.awt.GridBagConstraints.LINE_START;
import static java.awt.GridBagConstraints.PAGE_END;
import static java.awt.GridBagConstraints.PAGE_START;
import static javax.swing.BorderFactory.createLineBorder;
import static javax.swing.JFrame.EXIT_ON_CLOSE;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class GridBagLayoutTest {
    
    public static void main(String[] args) {
        JPanel pane = new JPanel(new GridBagLayout());
        pane.setBackground(red);
        pane.setPreferredSize(new Dimension(500, 500));
        // pane.setBorder(createEmptyBorder());
        // pane.setBorder(createLineBorder(white, 1));
        pane.setBorder(createLineBorder(white, 2));
        // pane.setBorder(createLineBorder(white, 3));
        
        GridBagConstraints c = new GridBagConstraints();
        c.weightx = 1;
        c.weighty = 1;
        c.gridx = 0;
        c.gridy = 0;
        c.anchor = FIRST_LINE_START;
        pane.add(rectangle(), c);
        c.gridx = 1;
        c.gridy = 0;
        c.anchor = PAGE_START;
        pane.add(rectangle(), c);
        c.gridx = 2;
        c.gridy = 0;
        c.anchor = FIRST_LINE_END;
        pane.add(rectangle(), c);
        c.gridx = 0;
        c.gridy = 1;
        c.anchor = LINE_START;
        pane.add(rectangle(), c);
        c.gridx = 1;
        c.gridy = 1;
        c.anchor = CENTER;
        pane.add(rectangle(), c);
        c.gridx = 2;
        c.gridy = 1;
        c.anchor = LINE_END;
        pane.add(rectangle(), c);
        c.gridx = 0;
        c.gridy = 2;
        c.anchor = LAST_LINE_START;
        pane.add(rectangle(), c);
        c.gridx = 1;
        c.gridy = 2;
        c.anchor = PAGE_END;
        pane.add(rectangle(), c);
        c.gridx = 2;
        c.gridy = 2;
        c.anchor = LAST_LINE_END;
        pane.add(rectangle(), c);
        
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(600, 600));
        frame.getContentPane().setLayout(new GridBagLayout());
        frame.getContentPane().setBackground(white);
        frame.getContentPane().add(pane);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static JPanel rectangle() {
        JPanel rect = new JPanel();
        rect.setBackground(white);
        rect.setPreferredSize(new Dimension(100, 100));
        return rect;
    }
}

Solution

  • this is a bug in JDK?

    Well you are trying to use the GridBagLayout in a way it wasn't designed to be used. You are trying to make a 5 component grid using 3 components in each row/column.

    The size of each cell is 500 / 3 = 166.666.

    When you try to set the location of each component you are getting some rounding issues. Sometimes it rounds up and sometimes down.

    To check this you can get rid of the setBorder(...) statement and then after the setVisible() statement add:

    for (Component panel: pane.getComponents())
        System.out.println(panel.getBounds());
    

    and you will see output like:

    java.awt.Rectangle[x=1,y=1,width=100,height=100]
    java.awt.Rectangle[x=200,y=1,width=100,height=100]
    java.awt.Rectangle[x=399,y=1,width=100,height=100]
    java.awt.Rectangle[x=1,y=200,width=100,height=100]
    java.awt.Rectangle[x=200,y=200,width=100,height=100]
    java.awt.Rectangle[x=399,y=200,width=100,height=100]
    java.awt.Rectangle[x=1,y=399,width=100,height=100]
    java.awt.Rectangle[x=200,y=399,width=100,height=100]
    java.awt.Rectangle[x=399,y=399,width=100,height=100]
    

    To fix the problem you can create a 5x5 grid and add each component to the appropriate cell in the grid:

    import static java.awt.Color.red;
    import static java.awt.Color.white;
    import static java.awt.GridBagConstraints.CENTER;
    import static java.awt.GridBagConstraints.FIRST_LINE_END;
    import static java.awt.GridBagConstraints.FIRST_LINE_START;
    import static java.awt.GridBagConstraints.LAST_LINE_END;
    import static java.awt.GridBagConstraints.LAST_LINE_START;
    import static java.awt.GridBagConstraints.LINE_END;
    import static java.awt.GridBagConstraints.LINE_START;
    import static java.awt.GridBagConstraints.PAGE_END;
    import static java.awt.GridBagConstraints.PAGE_START;
    import static javax.swing.BorderFactory.createLineBorder;
    import static javax.swing.JFrame.EXIT_ON_CLOSE;
    
    import java.awt.Dimension;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    import java.util.Arrays;
    
    public class GridBagLayoutTest2 {
    
        public static void main(String[] args) {
            GridBagLayout gbl = new GridBagLayout();
            JPanel pane = new JPanel(gbl);
            pane.setBackground(red);
    //        pane.setPreferredSize(new Dimension(500, 500));
            // pane.setBorder(createEmptyBorder());
            // pane.setBorder(createLineBorder(white, 1));
            pane.setBorder(createLineBorder(white, 2));
            // pane.setBorder(createLineBorder(white, 3));
    
    
            //  The minimimum width of a column is 100 pixels
            //  and the minimum height of a row is 100 pixels.
    
            int[] columns = new int[5];
            Arrays.fill(columns, 100);
            gbl.columnWidths = columns;
    
            int[] rows = new int[5];
            Arrays.fill(rows, 100);
            gbl.rowHeights = rows;
    
            GridBagConstraints c = new GridBagConstraints();
            c.weightx = 1;
            c.weighty = 1;
            c.gridx = 0;
            c.gridy = 0;
    //        c.anchor = FIRST_LINE_START;
            pane.add(rectangle(), c);
            c.gridx = 2;
            c.gridy = 0;
    //        c.anchor = PAGE_START;
            pane.add(rectangle(), c);
            c.gridx = 4;
            c.gridy = 0;
    //        c.anchor = FIRST_LINE_END;
            pane.add(rectangle(), c);
            c.gridx = 0;
            c.gridy = 2;
    //        c.anchor = LINE_START;
            pane.add(rectangle(), c);
            c.gridx = 2;
            c.gridy = 2;
    //        c.anchor = CENTER;
            pane.add(rectangle(), c);
            c.gridx = 4;
            c.gridy = 2;
    //        c.anchor = LINE_END;
            pane.add(rectangle(), c);
            c.gridx = 0;
            c.gridy = 4;
    //        c.anchor = LAST_LINE_START;
            pane.add(rectangle(), c);
            c.gridx = 2;
            c.gridy = 4;
    //        c.anchor = PAGE_END;
            pane.add(rectangle(), c);
            c.gridx = 4;
            c.gridy = 4;
    //        c.anchor = LAST_LINE_END;
            pane.add(rectangle(), c);
    
            JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
            frame.setPreferredSize(new Dimension(600, 600));
            frame.getContentPane().setLayout(new GridBagLayout());
            frame.getContentPane().setBackground(white);
            frame.getContentPane().add(pane);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static JPanel rectangle() {
            JPanel rect = new JPanel();
            rect.setBackground(white);
            rect.setPreferredSize(new Dimension(100, 100));
            return rect;
        }
    }
    

    For the reason this works see: Creating a board game layout using JLayeredPane

    Edit:

    The GridLayout has the same issue. if the width/height of the parent panel is not evenly divisible by the number of components in the GridLayout, then the components will not fill the entire space and the background of the parent panel will show through.

    As a hack for your provided code you could use:

    Border line = new LineBorder(Color.WHITE);
    Border empty = new EmptyBorder(-1, -1, -1, -1);
    Border compound = new CompoundBorder(line, empty);
    pane.setBorder(compound);
    

    This will allow the LineBorder to be painted without affecting the position of the components in the panel since the border insets will remain (0, 0, 0, 0).

    You should also be able to use a MatteBorder and only paint the bottom line and adjust the EmptyBorder accordingly.