【问题标题】:What's the easiest way to draw a (monochrome) array using a mouse with Swing?使用带有 Swing 的鼠标绘制(单色)数组的最简单方法是什么?
【发布时间】:2021-06-29 15:35:49
【问题描述】:

我一直在寻找一种在屏幕上绘制黑白数组的方法。这是一个简单的数组,只有 20x20。我打算做的是用鼠标在数组上绘制,以便每个像素在单击时从黑色“切换”到白色并返回,然后将数组作为一组布尔值(或整数)传递给另一个函数。目前我正在使用 Swing。我确实记得曾经使用 Swing 在画布上绘图,但我仍然找不到实际用法。我应该使用画布,还是依赖 JToggleButtons?

【问题讨论】:

    标签: java arrays swing


    【解决方案1】:

    您可以简单地使用 JFrame(或其他 Swing 组件)并覆盖 paint(Graphics) 方法来绘制布尔矩阵的表示(请注意,对于轻量级组件,例如 JPanel,您应该覆盖paintComponent(Graphics)。这将为您提供所需的单击和拖动功能(使用单个 Swing 组件的网格很难实现)。

    正如其他人评论的那样,AWT Canvas 不会为您提供 Swing 组件未提供的任何内容,您将在下面的示例中看到我使用了 createBufferStrategy 方法也存在于 JFrame 上确保不闪烁显示。

    请注意,我的示例非常简单,因为它会切换您拖过的每个像素,而不是通过单击操作来确定您是处于“绘画”模式还是“擦除”模式,然后在持续时间内专门应用黑色或白色像素的阻力。

    public class Grid extends JFrame {
        private static final int SCALE = 10; // 1 boolean value == 10 x 10 pixels.
        private static final int SIZE = 20;
    
        private boolean[][] matrix = new boolean[SIZE][SIZE];
        private boolean painting;
        private int lastX = -1;
        private int lastY = -1;
    
        public Grid() throws HeadlessException {
            setPreferredSize(new Dimension(SIZE * SCALE, SIZE * SCALE));
            setResizable(false);
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            setBackground(Color.WHITE);
    
            addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    painting = true;
                    tryAdjustValue(e.getPoint());
                }
    
                public void mouseReleased(MouseEvent e) {
                    painting = false;
                    lastX = -1;
                    lastY = -1;
                }
            });
    
            addMouseMotionListener(new MouseMotionListener() {
                public void mouseDragged(MouseEvent e) {
                    tryAdjustValue(e.getPoint());
                }
    
                public void mouseMoved(MouseEvent e) {
                    tryAdjustValue(e.getPoint());
                }
            });
        }
    
        private void tryAdjustValue(Point pt) {
            int newX = pt.x / SCALE;
            int newY = pt.y / SCALE;
    
            if (painting && isInRange(newX) && isInRange(newY) && (newX != lastX || newY != lastY)) {
                // Only invert "pixel" if we're currently in painting mode, both array indices are valid
                // and we're not attempting to adjust the same "pixel" as before (important for drag operations).
                matrix[newX][newY] = !matrix[newX][newY];
                lastX = newX;
                lastY = newY;
                repaint();
            }
        }
    
        private boolean isInRange(int val) {
            return val >= 0 && val < SIZE;
        }
    
        public void paint(Graphics g) {
            super.paint(g);
    
            for (int x=0; x<SIZE; ++x) {
                for (int y=0; y<SIZE; ++y) {
                    if (matrix[x][y]) {
                        g.fillRect(x * SCALE, y * SCALE, SCALE, SCALE);
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            Grid grid = new Grid();
            grid.pack();
            grid.setLocationRelativeTo(null);
            grid.createBufferStrategy(2);
            grid.setVisible(true);
        }
    }
    

    【讨论】:

    • 您的解决方案很好,只是您直接在JFrame 的paint 方法中而不是在JPanel 的paintComponent 方法中进行绘图。前者不受欢迎的原因有很多,如教程中所述。
    • 谢谢;我通常会在完整的应用程序中遵循这种方法,但希望让这个示例尽可能简单。
    【解决方案2】:

    为什么不在 GridLayout(20, 20) 中保存一个简单的 20 x 20 的 JPanel 网格,如果通过 MouseListener 的 mousePressed 方法单击则翻转面板的背景颜色。您可以将面板保存在二维数组中,并在需要时查询它们的背景颜色。

    您也可以为此使用 JLabels,但您必须记住将它们的 opaque 属性设置为 true。 JButton 或 JToggleButton 也可以工作……选项几乎是无限的。我不建议您使用 AWT (Canvas),因为它们不需要在功能上后退一步,因为 Swing 处理得很好。

    如果您对此感到困惑,为什么不回来向我们展示您的代码,我们将能够更好地为您提供更具体的帮助。

    解决此问题的另一种方法是使用单个 JPanel 并覆盖其 paintComponent 方法。您可以给它一个 int[][] 数组作为其模型,然后在 paintComponent 方法中根据模型的状态绘制所需颜色的矩形。然后给它一个 MouseListener 来改变模型的状态并调用 repaint。

    例如,

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class BlackWhiteGridPanel extends JPanel {
       // can have multiple colors if desired
       // public static final Color[] COLORS = {Color.black, Color.red, Color.blue, Color.white}; 
       public static final Color[] COLORS = {Color.black, Color.white};
       public static final int SIDE = 20;
       private static final int BWG_WIDTH = 400;
       private static final int BWG_HEIGHT = BWG_WIDTH;
    
       private int[][] model = new int[SIDE][SIDE]; // filled with 0's.
    
       public BlackWhiteGridPanel() {
          addMouseListener(new MouseAdapter() {
             @Override
             public void mousePressed(MouseEvent e) {
                myMousePressed(e);
             }
          });
       }
    
       private void myMousePressed(MouseEvent e) {
          // find relative position of mouse press on grid.
          int i = (e.getX() * SIDE) / getWidth();
          int j = (e.getY() * SIDE) / getHeight();
    
          int value = model[i][j];
          // the model can only hold states allowed by the COLORS array. 
          // So if only two colors, then value can only be 0 or 1.
          value = (value + 1) % COLORS.length;
          model[i][j] = value;
          repaint();
       }
    
       public int[][] getModel() {
          // return a copy of model so as not to risk corruption from outside classes 
          int[][] copy = new int[model.length][model[0].length];
          for (int i = 0; i < copy.length; i++) {
             System.arraycopy(model[i], 0, copy[i], 0, model[i].length);
          }
          return copy;
       }
    
       @Override
       protected void paintComponent(Graphics g) {
          super.paintComponent(g);
          int width = getWidth();
          int ht = getHeight();
          for (int i = 0; i < model.length; i++) {
             for (int j = 0; j < model[i].length; j++) {
                Color c = COLORS[model[i][j]];
                g.setColor(c);
                int x = (i * width) / SIDE;
                int y = (j * ht) / SIDE;
                int w = ((i + 1) * width) / SIDE - x;
                int h = ((j + 1) * ht) / SIDE - y;
                g.fillRect(x, y, w, h);
             }
          }
       }
    
       @Override
       public Dimension getPreferredSize() {
          return new Dimension(BWG_WIDTH, BWG_HEIGHT);
       }
    
       private static void createAndShowGui() {
          BlackWhiteGridPanel mainPanel = new BlackWhiteGridPanel();
    
          JFrame frame = new JFrame("BlackWhiteGrid");
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.getContentPane().add(mainPanel);
          frame.pack();
          frame.setLocationByPlatform(true);
          frame.setVisible(true);
       }
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                createAndShowGui();
             }
          });
       }
    }
    

    【讨论】:

    • 我不明白为什么使用 Canvas 结合覆盖 paint(Graphics) 方法来绘制单个像素是“功能上的倒退”。这是 Java 2D 应用程序的典型方法。
    • @Adamski 可能是 1998 年的典型。在 1.2 和 1.6 之间,混合 Swing 和 AWT 是有问题的。此外,Canvas 似乎不会为JComponentJPanel 无法处理的问题带来任何问题。
    • 目前我正在使用 GridLayout 和 20x20 JToggleButtons 对其进行一半修复,但效果不如我所愿。我期望的是按住鼠标按钮并画线(这是为神经网络捕获形状),所以我需要一个画布或类似的系统。
    • @CarlosSolís:然后请展示您的代码(或它的小型可编译版本),并尽可能多描述问题域。
    • @Andrew - 使用 Canvas、JComponent 还是 JPanel 并不是我真正的意思。我的观点是覆盖paint(Graphics) 来绘制数组很容易。提议的 Swing 解决方案很笨重,而且正如 Carlos 所说,它不能像预期的那样“画”线。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-15
    • 2011-03-03
    • 2020-11-07
    • 2012-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多