在多个 JButton 之上绘制

Drawing on top of multiple JButtons

所以,我有多个class方块的对象,也就是JButton的subclass。我有一个 class Board 实例,其中包含几个 Square 实例。我想要做的是当我按下其中一个按钮(正方形)时,在它上面画一个形状(一个圆圈)。为此,我在 Square class 中有一个布尔变量,即 isClicked,它基本上决定了在 paintComponent 方法中必须绘制的内容。

问题是当我有几个按钮时,按钮开始表现得很奇怪。令人惊讶的是,如果只有其中之一,则完全没有问题。一开始我以为是线程的问题,但是我把主要代码放到了invokeLater方法里面,一点用都没有。

我看到了一个使用 BufferedImage 的解决方案,但我想看看是否有可能按照我的方式解决问题。

抱歉,英语可能不完美。

方形 class:

public class Square extends JButton implements ActionListener {

private int number;
private boolean isClicked;

public Square(int x) {
    number = x;
    isClicked = false;
}

@Override
protected void paintComponent(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;
    if (!isClicked) {
        super.paintComponent(g);
    } else {
        System.out.println("EXECUTED for: " + number);
        g2d.drawOval(this.getX(), this.getY(), 100, 100);
    }
}

@Override
public void actionPerformed(ActionEvent e) {
    isClicked = !isClicked;
    System.out.println(isClicked + " " + number);
    repaint();
}

}

棋盘class:

public class Board extends JPanel {

private static final int BOARD_WIDTH = (int) (TicTacToe.WIDTH * 0.7);
private static final int VERTICAL_LINE_LENGTH = (int) (TicTacToe.WIDTH * 0.5);
private static final int HORIZONTAL_LINE_LENGTH = (int) (TicTacToe.HEIGHT * 0.8);
private static final int STROKE_WIDTH = 5;

private Square[] squares;

public Board() {

}

public void addButtons() {
    squares = new Square[9];

    for (int i = 0; i < 3; i++) {
            Square square = new Square(i);
            square.setPreferredSize(new Dimension(30, 30));
            square.addActionListener(square);
            this.add(square);
            squares[i] = square;
            ((GridLayout)this.getLayout()).setHgap(30);
            ((GridLayout)this.getLayout()).setVgap(30);
    }
}

public Square[] getButtons() {
    return squares;
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    g2d.setStroke(new BasicStroke(STROKE_WIDTH));
    // Horiztontal lines
    g2d.drawLine(0, TicTacToe.HEIGHT / 3,
            BOARD_WIDTH, TicTacToe.HEIGHT / 3);
    g2d.drawLine(0, 2 * TicTacToe.HEIGHT / 3,
            BOARD_WIDTH, 2 * TicTacToe.HEIGHT / 3);
    // Vertical lines
    g2d.drawLine(BOARD_WIDTH / 3, 0, BOARD_WIDTH / 3,
            TicTacToe.HEIGHT);
    g2d.drawLine(2 * BOARD_WIDTH / 3, 0, 2 * BOARD_WIDTH / 3,
            TicTacToe.HEIGHT);

}
}

主要方法:

SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            Board board = new Board();
    board.setPreferredSize(new Dimension((int) (WIDTH * 0.7), HEIGHT));
    board.setLayout(new GridLayout(3, 3));
    board.addButtons();

    GameOptions opt = new GameOptions();
    opt.setPreferredSize(new Dimension((int) (WIDTH * 0.3), HEIGHT));


    JFrame frame = new JFrame("Tic Tac Toe");
    frame.setLayout(new FlowLayout());
    frame.add(board);
    frame.add(opt);

    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
        }
    });

您在按钮的绘图代码上使用 getX()getY() 是完全错误的,不属于。这些方法 return 按钮相对于其容器的位置,因此虽然这可能适用于位于左上角的按钮,但它会失败,因为您最终会在远离容器的地方绘制按钮本身,以及您的许多绘图将永远不会显示。

最好不要扩展 JButton,而是简单地交换显示您想要在 JButton 上绘制的内容的 ImageIcons。这要简单得多,也更不容易白痴。您可以通过调用 .setIcon(myImageIcon) 来设置按钮的图标,传入选择的图标。

但如果您绝对想在按钮上绘图,则无需使用 getX()getY()。您可能还想使用 JToggleButton 作为父级 class,因为您正在切换状态。比如我的MCVE:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.BufferedImage;
import javax.swing.*;

@SuppressWarnings("serial")
public class DrawButtonPanel extends JPanel {
    private static final int SIDE = 3;
    private static final int GAP = 5;
    private static final Color BG = Color.BLACK;

    public DrawButtonPanel() {
        setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
        setLayout(new GridLayout(SIDE, SIDE, GAP, GAP));
        setBackground(BG);
        for (int i = 0; i < SIDE * SIDE; i++) {
            // add(new DrawButton1());
            DrawButton2 drawButton2 = new DrawButton2(i);
            AbstractButton button = drawButton2.getButton();
            add(button);
        }
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new DrawButtonPanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

class DrawButton2 {
    private static final int PREF_W = 200;
    private static final int PREF_H = PREF_W;
    private static final int GAP = 20;
    private static final float STROKE_WIDTH = 15f;
    private static final Stroke BASIC_STROKE = new BasicStroke(STROKE_WIDTH);
    private static final Color COLOR = Color.RED;
    private static final Color BG = Color.LIGHT_GRAY;
    private AbstractButton button = new JToggleButton();
    private int index;

    public DrawButton2(int index) {
        this.index = index;
        button.setBorderPainted(false);
        button.setBorder(null);
        button.setIcon(createPlainIcon());
        button.setSelectedIcon(createSelectedIcon());

        button.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    System.out.println("Index: " + index);
                }
            }
        });
    }

    public int getIndex() {
        return index;
    }

    private Icon createPlainIcon() {
        BufferedImage img = new BufferedImage(PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        g2d.setColor(new Color(0, 0, 0, 0));
        g2d.fillRect(0, 0, PREF_W, PREF_H);
        g2d.dispose();
        return new ImageIcon(img);
    }

    private Icon createSelectedIcon() {
        BufferedImage img = new BufferedImage(PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(BASIC_STROKE);
        g2d.setColor(BG);
        g2d.fillRect(0, 0, PREF_W, PREF_H);
        g2d.setColor(COLOR);
        g2d.drawOval(GAP, GAP, PREF_W - 2 * GAP, PREF_H - 2 * GAP);
        g2d.dispose();
        return new ImageIcon(img);
    }

    public AbstractButton getButton() {
        return button;
    }

}

@SuppressWarnings("serial")
class DrawButton1 extends JToggleButton {
    private static final int PREF_W = 200;
    private static final int PREF_H = PREF_W;
    private static final int GAP = 20;
    private static final float STROKE_WIDTH = 15f;
    private static final Stroke BASIC_STROKE = new BasicStroke(STROKE_WIDTH);
    private static final Color COLOR = Color.RED;
    private static final Color BG = Color.LIGHT_GRAY;

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        return new Dimension(PREF_W, PREF_H);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(BASIC_STROKE);
        if (!isSelected()) {
            super.paintComponent(g);
        } else {
            g2d.setColor(BG);
            g2d.fillRect(0, 0, getWidth(), getHeight());
            g2d.setColor(COLOR);
            g2d.drawOval(GAP, GAP, getWidth() - 2 * GAP, getHeight() - 2 * GAP);
        }
        g2d.dispose(); // since we created a new one
    }
}

编辑以展示如何使用 JToggleButton 和图标执行此操作。