如何使用Java中的Swing类绘制网格,并在单击和拖动时检测鼠标位置

6

我正在尝试使用Swing类创建一个网格UI(5 * 5)。我试过使用嵌套循环,并动态地向jFrame添加jPanel。我还尝试在用户单击并放置在其上时更改每个jPanel的背景颜色。 但是,我的代码中每个单元格之间存在巨大的间隙,并且我无法使拖动事件正常工作。

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);
              }
           }
    }
}

现在我正在尝试创建一个面板并在其上绘制网格,然后检测鼠标在网格上的位置,进而改变每个单元格的颜色。

有没有人能给我一些关于如何在JPanel上实现此网格,并更改所选单元格颜色的建议。

3个回答

32
有很多方法可以让它工作,具体取决于您想要实现什么目标。这个第一个例子简单地使用2D图形API来呈现单元格和MouseMotionListener来监视哪个单元格被突出显示。 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();
        }
    }
}

这个例子可以随窗口大小改变网格大小,但将单元格设为固定大小只需要稍作修改。

查看2D图形了解更多细节

组件示例更新

此示例使用一系列JPanel来表示每个单元格。

每个单元格都定义了固定的宽度和高度,并且不随主窗口调整大小。

输入图像描述

在这个例子中,每个单元格面板都有自己的鼠标监听器。重新编写代码,使主面板只有一个鼠标监听器并自行管理工作量也不会太困难。

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);
        }
    }
}

@Twhite1195 哪一个版本,第一版还是第二版? - MadProgrammer
@MadProgrammer 首先,第二个方法很好用... 直到我调整了单元格大小。 - Twhite1195
@Twhite1195 你的 columnCountrowCount 值是多少? - MadProgrammer
@MadProgrammer columnCount 是50,而 rowCount 是36。 - Twhite1195
@Twhite1195 我已更新代码,基本上,在单元格被绘制时,应用了x/y偏移来使单元格在容器中居中,但 MouseListener 没有注意到这一点。 - MadProgrammer
显示剩余2条评论

1

我不喜欢边框的渲染,因为在网格内部,如果有多个边框,则会出现重复的情况。我认为这个解决方案更好:

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);
    }
}

编辑:

我的解决方案更好,因为如果原始代码是5x5个单元格,但更多,比如10x10……一些单元格的内部边缘将会接触,并创建一些地方的粗网格。在截图中看起来很不错。 粗网格


你能否编辑你的答案,解释一下为什么你的解决方案可以解决这个问题? - Kyll

0
在MouseListener示例中的mouseMoved方法中,您可能需要考虑xOffset / yOffset以获得更平滑的单元格识别。
int column = (x - xOffset) / cellWidth;
int row = (y - yOffset) / cellHeight;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接