6

I am trying to create a grid UI (5*5) using Swing classes. I tried a nested loop and adding a jPanel dynamically to the jFrame. And I also tried to change the background colour of each jPanel when user clicks and drops over it. But with my code there are huge gaps between each cell and I can't get the drag event to work.

public class clsCanvasPanel extends JPanel {
    private static final int intRows = 5;
    private static final int intCols = 5;
    private List<JPanel> jpllist = new ArrayList<JPanel>();

    public clsCanvasPanel() {                           
        /*
         * 
         * Add eventListener to individual JPanel within CanvasPanel
         *
         * 
         * TODO : 
         * 1) mousePressed --> update Temperature and HeatConstant of clsElement Class
         * 2) start a new thread and  
         * 3) call clsElement.run() method
         * 
         * 
         * Right Now : it updates the colours of the JPanel
         * */
          MouseListener mouseListener = new MouseAdapter() {
             @Override
             public void mousePressed(MouseEvent e) {
                JPanel panel = (JPanel) e.getSource();

                Component[] components = panel.getComponents();
                for (Component component : components) {
                   component.setVisible(!component.isVisible());
                   component.setBackground(new Color(255,255,0));
                }
                panel.revalidate();
                panel.repaint();
             }
          };

          //TODO : refactoring
          GridLayout gdlyPlates = new GridLayout();
          gdlyPlates.setColumns(intCols);
          gdlyPlates.setRows(intRows);
          gdlyPlates.setHgap(0);
          gdlyPlates.setVgap(0);
          setLayout(gdlyPlates);

          //TODO : refactoring
          for (int row = 0; row < intRows; row++) {
              for (int col = 0; col < intCols; col++) {
                 JPanel panel = new JPanel(new GridBagLayout());
                 panel.setOpaque(false);
                 JPanel jl = new JPanel();
                 jl.setVisible(true);
                 panel.add(jl);
                 panel.addMouseListener(mouseListener);
                 jpllist.add(panel);
                 add(panel);
              }
           }
    }
}

So now I am trying to create one panel and draw grids on it, then detects the mouse position on the grid, further change the colour of each cell.

Could someone give me some advices on how to implement this grid on JPanel, and change the colour of a chosen cell.

CubeJockey
  • 2,209
  • 8
  • 24
  • 31
June
  • 974
  • 5
  • 20
  • 34

3 Answers3

32

There are any number of ways to get this to work, depending on what it is you want to achieve.

This first example simply uses the 2D Graphics API to render the cells and a MouseMotionListener to monitor which cell is highlighted.

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestGrid01 {

    public static void main(String[] args) {
        new TestGrid01();
    }

    public TestGrid01() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private int columnCount = 5;
        private int rowCount = 5;
        private List<Rectangle> cells;
        private Point selectedCell;

        public TestPane() {
            cells = new ArrayList<>(columnCount * rowCount);
            MouseAdapter mouseHandler;
            mouseHandler = new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    Point point = e.getPoint();

                    int width = getWidth();
                    int height = getHeight();

                    int cellWidth = width / columnCount;
                    int cellHeight = height / rowCount;

                    selectedCell = null;
                    if (e.getX() >= xOffset && e.getY() >= yOffset) {

                        int column = (e.getX() - xOffset) / cellWidth;
                        int row = (e.getY() - yOffset) / cellHeight;

                        if (column >= 0 && row >= 0 && column < columnCount && row < rowCount) {

                            selectedCell = new Point(column, row);

                        }

                    }
                    repaint();

                }
            };
            addMouseMotionListener(mouseHandler);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        public void invalidate() {
            cells.clear();
            selectedCell = null;
            super.invalidate();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            int width = getWidth();
            int height = getHeight();

            int cellWidth = width / columnCount;
            int cellHeight = height / rowCount;

            int xOffset = (width - (columnCount * cellWidth)) / 2;
            int yOffset = (height - (rowCount * cellHeight)) / 2;

            if (cells.isEmpty()) {
                for (int row = 0; row < rowCount; row++) {
                    for (int col = 0; col < columnCount; col++) {
                        Rectangle cell = new Rectangle(
                                xOffset + (col * cellWidth),
                                yOffset + (row * cellHeight),
                                cellWidth,
                                cellHeight);
                        cells.add(cell);
                    }
                }
            }

            if (selectedCell != null) {

                int index = selectedCell.x + (selectedCell.y * columnCount);
                Rectangle cell = cells.get(index);
                g2d.setColor(Color.BLUE);
                g2d.fill(cell);

            }

            g2d.setColor(Color.GRAY);
            for (Rectangle cell : cells) {
                g2d.draw(cell);
            }

            g2d.dispose();
        }
    }
}

This example does resize the grid with the window, but it would be a trivial change to make the cells fixed size.

Check out 2D Graphics for more details

Update with component example

This example uses a series of JPanels to represent each cell.

Each cell is defined with a fixed width and height and do not resize with the main window.

enter image description here

In this example, each cell panel has it's own mouse listener. It wouldn't be overly difficult to re-code it so that the main panel had a single mouse listener and managed the work load itself instead.

import java.awt.BorderLayout;
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.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.MatteBorder;

public class TestGrid02 {

    public static void main(String[] args) {
        new TestGrid02();
    }

    public TestGrid02() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                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 row = 0; row < 5; row++) {
                for (int col = 0; col < 5; col++) {
                    gbc.gridx = col;
                    gbc.gridy = row;

                    CellPane cellPane = new CellPane();
                    Border border = null;
                    if (row < 4) {
                        if (col < 4) {
                            border = new MatteBorder(1, 1, 0, 0, Color.GRAY);
                        } else {
                            border = new MatteBorder(1, 1, 0, 1, Color.GRAY);
                        }
                    } else {
                        if (col < 4) {
                            border = new MatteBorder(1, 1, 1, 0, Color.GRAY);
                        } else {
                            border = new MatteBorder(1, 1, 1, 1, Color.GRAY);
                        }
                    }
                    cellPane.setBorder(border);
                    add(cellPane, gbc);
                }
            }
        }
    }

    public class CellPane extends JPanel {

        private Color defaultBackground;

        public CellPane() {
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    defaultBackground = getBackground();
                    setBackground(Color.BLUE);
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    setBackground(defaultBackground);
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(50, 50);
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • hello, I realize this is a reaaally old post. Hey @MadProgrammer, your code is amazing!, I grabbed it to tinker with it for an assignment I have , but I'm in a bit of a pickle, this is exactly what I need, but I need to make the window 1080x760, have 1700 cells and make each cell 20x20. Now I've rezized the window, and I made the 1700 cells, the problem I'm having is that when I try to resize the cells to 20x20 the mouse listener goes mad and doesn't work properly... I've tried changing the cellWidth and cellHeight variables... but that isn't working so well... any thoughts? – Twhite1195 Oct 23 '15 at 06:22
  • @Twhite1195 Which version, the first or second? – MadProgrammer Oct 23 '15 at 06:25
  • @MadProgrammer First, the second one worked great... until I resized the cells – Twhite1195 Oct 23 '15 at 06:26
  • @Twhite1195 What's you're `columnCount` and `rowCount` values? – MadProgrammer Oct 23 '15 at 06:29
  • @MadProgrammer `columnCount` is 50 and `rowCount` is 36 – Twhite1195 Oct 23 '15 at 06:31
  • @Twhite1195 I've updated the code, basically, when the cells are painted, there is a x/y offset applied to keep the cells centered within the container, but the `MouseListener` wasn't paying attention to this – MadProgrammer Oct 23 '15 at 06:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/93129/discussion-between-twhite1195-and-madprogrammer). – Twhite1195 Oct 23 '15 at 06:38
1

I did not like rendering of borders since inside the grid some were duplicated if there were more than the example. I think that this solution is better:

private int width;
private int height;

// ...

for (int row = 0; row <= this.height; row++) {
    for (int col = 0; col <= this.width; col++) {
        gbc.gridx = col;
        gbc.gridy = row;

        CellPane cellPane = new CellPane();
        Border border = new MatteBorder(1, 1, (row == this.height ? 1 : 0), (col == this.width ? 1 : 0), Color.GRAY);

        cellPane.setBorder(border);
        this.add(cellPane, gbc);
    }
}

Edit:

My solution is better, because if the original code will be 5x5 cells, but more, such as 10x10 ... inner edges of some cells will be in contact and to create some places thick grid. It's nice to see on the screenshot thick grid

0

In the MouseListener example in the mouseMoved method, you might want to consider the xOffset/yOffset though for a smoother cell recognition.

int column = (x - xOffset) / cellWidth;
int row = (y - yOffset) / cellHeight;
Pang
  • 9,564
  • 146
  • 81
  • 122