简体   繁体   中英

What's the easiest way to draw a (monochrome) array using a mouse with Swing?

I've been searching for a way to draw a black-and-white array on screen. It's a simple array, just 20x20. What I plan to do is to draw on an array with the mouse so that each pixel "toggles" from black to white and back when clicked, then pass the array as a set of booleans (or integers) to another function. Currently I'm using Swing. I do remember to have used Swing for drawing on a canvas, but I still can't find the actual usage. Should I use a canvas, or instead rely on JToggleButtons?

You can simply use a JFrame (or other Swing component) and override the paint(Graphics) method to draw a representation of the boolean matrix (note that in the case of a lightweight component such as JPanel you should override paintComponent(Graphics) . This will give you the click-and-drag capability you require (which is very difficult to achieve using a grid of individual Swing components).

As other people have commented, AWT Canvas doesn't give you anything not provided by Swing components and you'll see in the example below that I've used the createBufferStrategy method also present on JFrame to ensure a non-flicker display.

笑脸

Note that my example is fairly simple in that it toggles every pixel you drag across rather than the click operation establishing whether you're in "paint" mode or "erase" mode and then exclusively applying black or white pixels for the duration of the drag.

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

Why not a simple 20 x 20 grid of JPanel held in a GridLayout(20, 20), and flip the panel's background color if clicked via a MouseListener's mousePressed method. You could hold the panels in a 2D array and query their background color whenever the need arises.

You could also use JLabels for this, but you'd have to remember to turn their opaque properties to true. A JButton would work as well or a JToggleButton, ... the options are almost limitless. I do not recommend though that you use AWT (Canvas) as their's no need to step backwards in functionality since Swing handles this so well.

If you get stuck on this, why not come back and show us your code and we'll better be able to give you more specific help.

Another way to solve this is to use a single JPanel and override its paintComponent method. You could give it an int[][] array to serve as its model, and then in the paintComponent method draw rectangles of whatever color desired based on the state of the model. Then give it a MouseListener that changes the state of the model and calls repaint.

eg,

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM