我最初尝试对画布内的几何形状执行命中测试,检查鼠标单击位置是否在形状的边界内。然而,结果并没有达到预期。
因此,我创建了一个最小的、可重现的示例。此示例中的代码片段根据鼠标的位置在画布中创建一个矩形。当我们选择“选择”RadioButton 并在画布上的矩形范围内单击时,我期望 hit 方法返回 true,但实际上返回 false。
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.MouseEvent;
class CanvasArea extends JPanel {
Rectangle rectangle = new Rectangle();
MouseInputAdapter mode;
Graphics2D storedG2d;
CanvasArea() {
setBackground(Color.WHITE);
setPreferredSize(new Dimension(1000, 600));
}
void setRectangle(Rectangle r) {
repaint(rectangle.x, rectangle.y, rectangle.width + 1, rectangle.height + 1);
rectangle = r;
repaint(rectangle.x, rectangle.y, rectangle.width + 1, rectangle.height + 1);
}
void setMode(MouseInputAdapter mode) {
removeMouseListener(this.mode);
this.mode = mode;
addMouseListener(this.mode);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.draw(rectangle);
storedG2d = (Graphics2D) g2d.create();
}
}
class RectangleListener extends MouseInputAdapter {
@Override
public void mouseClicked(MouseEvent e) {
HitTest.canvas.setRectangle(new Rectangle(e.getPoint(), new Dimension(100, 120)));
}
}
class SelectListener extends MouseInputAdapter {
@Override
public void mousePressed(MouseEvent e) {
System.out.println(e.getPoint());
System.out.println(HitTest.canvas.rectangle);
Rectangle mousePosition = new Rectangle(e.getPoint(), new Dimension(1, 1));
System.out.println(HitTest.canvas.storedG2d.hit(mousePosition, HitTest.canvas.rectangle, false));
}
}
public class HitTest {
static CanvasArea canvas = new CanvasArea();
private Container createContentPane() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setOpaque(true);
JRadioButton rb1 = new JRadioButton("rectangle");
rb1.addActionListener(e -> canvas.setMode(new RectangleListener()));
JRadioButton rb2 = new JRadioButton("select");
rb2.addActionListener(e -> canvas.setMode(new SelectListener()));
JToolBar toolBar = new JToolBar(JToolBar.VERTICAL);
ButtonGroup group = new ButtonGroup();
toolBar.add(rb1);
group.add(rb1);
toolBar.add(rb2);
group.add(rb2);
contentPane.add(toolBar, BorderLayout.LINE_START);
contentPane.add(canvas, BorderLayout.CENTER);
return contentPane;
}
private static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
HitTest hitTest = new HitTest();
frame.setContentPane(hitTest.createContentPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(HitTest::createAndShowGUI);
}
}
可以观察到鼠标点击位置在矩形范围内,但是hit方法返回false。
我使用调试进入了 hit 方法,并注意到矩形的位置和尺寸正在被转换(根据 hit 方法的文档,我假设它已转换为设备空间,但我不确定) 。变换后矩形与鼠标点击位置不相交,导致返回值错误。我不确定如何继续获得正确的结果。
恕我直言,这个...
class CanvasArea extends JPanel {
Graphics2D storedG2d;
//...
@Override
protected void paintComponent(Graphics g) {
//...
storedG2d = (Graphics2D) g2d.create();
}
}
是个坏主意。您不应该保留系统
Graphics
上下文的引用,因为很多事情都可能会出错。
我从未使用过
Graphics2D#hit
,我一直使用 Shape
API,它有自己的内置命中检测功能。
但是,如果我要使用
Graphics2D#hit
,我会在 paintComponent
方法中执行此操作,例如...
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 javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MainPane extends JPanel {
private DrawPane drawPane;
public MainPane() {
setLayout(new BorderLayout());
drawPane = new DrawPane();
add(drawPane);
drawPane.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
drawPane.didHit(e.getPoint());
}
});
}
}
public class DrawPane extends JPanel {
private Rectangle rectangle;
private Rectangle hitBounds;
final private Dimension hitSize = new Dimension(1, 1);
public DrawPane() {
rectangle = new Rectangle(150, 150, 100, 100);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
// It's VERY important that the Point is within the context of the
// this component, otherwise you will get werid results
public void didHit(Point p) {
hitBounds = new Rectangle(p, hitSize);
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
if (hitBounds != null) {
if (g2d.hit(hitBounds, rectangle, false)) {
g2d.fill(rectangle);
} else {
g2d.draw(rectangle);
}
} else {
g2d.draw(rectangle);
}
g2d.dispose();
}
}
}
但这引发了一系列新问题,因为您应该避免在
paintComponent
中使用太多逻辑……以及如何标记/通知发生命中的事实?
相反,我会使用
Shape#contains(Point)
方法来进行命中检测。这可以独立于 Graphics
上下文和绘画工作流程来完成,这使得它更容易用作“更新”阶段或其他条件工作流程的一部分。
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 javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MainPane extends JPanel {
private DrawPane drawPane;
public MainPane() {
setLayout(new BorderLayout());
drawPane = new DrawPane();
add(drawPane);
drawPane.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
drawPane.didHit(e.getPoint());
}
});
}
}
public class DrawPane extends JPanel {
private Rectangle rectangle;
private boolean hit = false;
public DrawPane() {
rectangle = new Rectangle(150, 150, 100, 100);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
// It's VERY important that the Point is within the context of the
// this component, otherwise you will get werid results
public boolean didHit(Point p) {
hit = false;
if (rectangle.contains(p)) {
hit = true;
}
repaint();
return hit;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
if (hit) {
g2d.fill(rectangle);
} else {
g2d.draw(rectangle);
}
g2d.dispose();
}
}
}
这还允许您将
Shape
的管理与绘制工作流程分离,支持“模型-视图-控制器”等概念