Graphics2D.hit 方法返回意外的布尔值

问题描述 投票:0回答:1

我最初尝试对画布内的几何形状执行命中测试,检查鼠标单击位置是否在形状的边界内。然而,结果并没有达到预期。

因此,我创建了一个最小的、可重现的示例。此示例中的代码片段根据鼠标的位置在画布中创建一个矩形。当我们选择“选择”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 方法的文档,我假设它已转换为设备空间,但我不确定) 。变换后矩形与鼠标点击位置不相交,导致返回值错误。我不确定如何继续获得正确的结果。

java swing detection hittest
1个回答
0
投票

恕我直言,这个...

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
的管理与绘制工作流程分离,支持“模型-视图-控制器”等概念

© www.soinside.com 2019 - 2024. All rights reserved.