Search code examples
javaswingjbuttongrid-layout

Adding Grid of Buttons to JPanel


I'm trying to make a grid of 20px x 20px buttons that I define as "cells", blank by default, no special decorations such as shading, and change color when clicked. (They are meant to show "1" just for testing purposes). I make a Cell class to define these buttons, give each an ActionListener.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Cell implements ActionListener
{
    private JButton button;
    private EditorPanel editorPanel;

    public Cell(EditorPanel editorPanel){
    button = new JButton("1'");
    button.addActionListener(listener -> colorCell());
    button.setPreferredSize(new Dimension(20,20));
    button.setMargin(new Insets(0,0,0,0));
    button.setOpaque(true);
    button.setContentAreaFilled(false);
    this.editorPanel = editorPanel;
    }

public JButton getButton() {
    return button;
}

public void colorCell()
{
    button.setBackground(Color.BLACK);
}

@Override
public void actionPerformed(ActionEvent e) {

}

}

and then use an array of Cell objects (cells) in my EditorPanel class to create a grid of these buttons with dimensions defined by "col" and "row".

import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;


import javax.swing.JFrame;
import javax.swing.JPanel;
public class EditorPanel{

public JFrame jframe;
public JPanel jpanel;
public static EditorPanel editorPanel;
public Render render;
public static final int col = 45, row = 45,  tile_size=20;
public static final int panelWidth=900, panelHeight=900;
public Dimension dim;
public int coloredPixels;

public Cell[][] cells; 

public void getFrame() {

    editorPanel = new EditorPanel();
    dim = Toolkit.getDefaultToolkit().getScreenSize();
    jframe = new JFrame("Pixel Art Creator");
    jframe.setVisible(true);
    jframe.setSize(panelWidth+17, panelHeight+40);
    jframe.setLocation(dim.width/2 - jframe.getWidth()/2, dim.height/2 - jframe.getHeight()/2);
    jframe.add(render = new Render());
    jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

private JPanel addCells()
{
    cells=new Cell[row][col];
    JPanel panel = new JPanel(new GridLayout(row, col));
    for(int i = 0; i< row; i++){
        for(int j = 0; j<col; j++){
            cells[i][j] = new Cell(this);
            panel.add(cells[i][j].getButton());
        }
    }
    return panel;
}

public static void main (String[] args)
{
    editorPanel = new EditorPanel();
    editorPanel.getFrame();
    editorPanel.addCells();
}

}

I then try to add each created Cell object that I attempted to put into the cells array in the addCells() method and add it to my JPanel. When I run this code I don't get any buttons, meaning that these buttons aren't being added to JPanel. How should I go about this?


Solution

  • So, two "significant" issues:

    1. editorPanel.addCells(); never adds the JPanel that it creates to anything, so it will never be displayed
    2. Calling JFrame#setVisible BEFORE you've finished establishing the UI can cause the UI elements not to show up on the UI. You can fix this by calling revalidate and repaint on the container which is changed, but if possible, simply get the UI established first, then make it visible

    I would, however, suggest a slight change in approach. Rather then making Cell a class which contains a JButton, and then exposing that button to other aspects of your UI, I would make Cell a component and simply add it to the what ever container you want, for example...

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.border.LineBorder;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame("Test");
                    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();
    
                for (int y = 0; y < 20; y++) {
                    gbc.gridy = y;
                    for (int x = 0; x < 20; x++) {
                        gbc.gridx = x;
                        add(new Cell(), gbc);
                    }
                }
            }
    
        }
    
        public class Cell extends JPanel {
    
            public Cell() {
                addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        colorCell();
                    }
                });
                setBorder(new LineBorder(Color.GRAY));
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(20, 20);
            }
    
            protected void colorCell() {
                if (getBackground() != Color.DARK_GRAY) {
                    setBackground(Color.DARK_GRAY);
                } else {
                    setBackground(null);
                }
            }
    
        }
    
    }
    

    Now, I've just used a plain old JPanel in the this case, but you could just as easily extend from a JButton or JToggledButton ... but I might be tempted to use a factory pattern instead, but that's me.

    The purpose of using a GridBagLayout is to allow the frame and outer containers to be resized without changing the size of the Cells themselves, unlike GridLayout, which will try and make the cells fill the available space