Search code examples
javaswingjframelayout-managergrid-layout

GridLayout is showing odd behavior


I am trying to create a grid comprised of 100 squares. My code below is extremely buggy and I am not sure why.

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

public class snake extends JFrame
{
    public static void main(String[] args)
    {
        Border whiteLine = BorderFactory.createLineBorder(Color.white);
        //-----------FRAME
        JFrame frame = new JFrame();
        frame.setSize(1000,1000);
        frame.setTitle("Snake");
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        frame.getContentPane().setBackground(Color.black);
        frame.setVisible(true);
        frame.setLayout(new GridLayout(10,10));
        //-----------FRAME

        //-----------PANELS
        Dimension panelDimension = new Dimension(20,20);

        int counter = 0;
        JPanel[][] p = new JPanel[10][10];
        for (int i = 0; i < 10; i++)
        {
            for (int j = 0; j < 10; j++)
            {
                p[i][j] = new JPanel();
                //p[i][j].setPreferredSize(panelDimension);
                p[i][j].setBackground(Color.red);
                //p[i][j].setLocation(490,490);
                p[i][j].setBorder(whiteLine);
                p[i][j].setVisible(true);

                frame.getContentPane().add(p[i][j]);

                counter+=1;
            }
        }
       System.out.println("counter: " + counter);


    }
}

When I run the code like this it shows a grid comprised of 2 columns the first column has 7 rows and the second column has 6. Sometimes it even shows other incorrect numbers of columns and rows. I am not sure why it doesn't create a grid of 10 rows 10 columns.


Solution

  • You've got several problems including:

    • Calling setVisible(true) on the JFrame before adding components, before calling pack() on the top-level window. This can lead to wonky positioned components within our GUI's or even GUI's that remain empty
    • Not calling pack() on the JFrame after adding components and before setting it visible
    • Setting the size of the JFrame. Let the layout managers, containers and components do this for you (which is what calling pack() is for)
    • Setting it to a bad size, a "perfect square", one that ignores the menu bar that the OS adds,
    • ...

    For example:

    package foo01;
    
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.GridLayout;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class SnakePanel extends JPanel {
        private static final int CELL_WIDTH = 80;
        private static final Dimension CELL_DIMENSION = new Dimension(CELL_WIDTH, CELL_WIDTH);
        private static final int COLUMNS = 10;
        private static final int GAP = 2;
        private static final Color BG_COLOR = Color.WHITE;
        private static final Color CELL_COLOR = Color.RED;
    
        public SnakePanel() {
            setBackground(BG_COLOR);
            
            // add a white line around the grid
            setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
            
            // create a grid with gaps that show the background (white) color
            setLayout(new GridLayout(COLUMNS, COLUMNS, GAP, GAP));
    
            for (int row = 0; row < COLUMNS; row++) {
                for (int col = 0; col < COLUMNS; col++) {
                    JPanel cell = new JPanel(); // create a new cell
                    cell.setPreferredSize(CELL_DIMENSION); // cheating here. Better to override getPreferredSize()
                    cell.setBackground(CELL_COLOR);
                    add(cell);
                    
                    // give the cell JPanel some simple behavior:
                    cell.addMouseListener(new MyMouse(col, row));
                }
            }
        }
    
        private class MyMouse extends MouseAdapter {
            private int col;
            private int row;
    
            public MyMouse(int col, int row) {
                this.col = col;
                this.row = row;
            }
            
            @Override
            public void mousePressed(MouseEvent e) {
                System.out.printf("Mouse pressed row and column: [%d, %d]%n", row, col);
            }
        }
        
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                // create the main JPanel
                SnakePanel snakePanel = new SnakePanel();
    
                // create the JFrame
                JFrame frame = new JFrame("Snake");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                
                // add the main JPanel to the JFrame
                frame.add(snakePanel);
                
                // pack the JFrame -- tells the layout managers to do their things
                frame.pack();
                
                // if we want to center the GUI:
                frame.setLocationRelativeTo(null);
                
                // only *now* do we display the GUI
                frame.setVisible(true);
            });
        }
    }
    

    Some notes on the code:

    Any code within the Runnable passed into the SwingUtilities.invokeLater(...) method is called on the Swing event thread, which is a wise thing to do when creating a Swing GUI

    SwingUtilities.invokeLater(() -> {
        // ....
    });
    

    First, create the main JPanel that is held by the JFrame:

    SnakePanel snakePanel = new SnakePanel();
    

    Then create the JFrame, add that JPanel and call pack(). The pack call tells the layout managers to do there thing, to lay out components within containers, to size things based on their preferred sizes and their layouts:

    JFrame frame = new JFrame("Snake");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(snakePanel);
    frame.pack();
    

    if we want to center the GUI:

    frame.setLocationRelativeTo(null);
    

    only now do we display the GUI

    frame.setVisible(true);