Java MouseEvent position is inaccurate

耗尽温柔 提交于 2019-12-18 18:27:06

问题


I've got a problem in Java using a "canvas" class I created, which is an extended JPanel, to draw an animated ring chart. This chart is using a MouseListener to fetch click events.

The problem is that the mouse position does not seem to be accurate, meaning it does not seem to be relative to the "canvas" but instead relative to the window (in the left, upper corner I got about 30px for y coord).

This is my code:

I created a class, that extends JPanel and does have a BufferedImage as member.

public class Canvas extends JPanel {

    public BufferedImage buf;
    private RingChart _parent;

    public Canvas(int width, int height, RingChart parent){
        buf = new BufferedImage(width, height, 1);
    ...

In the paint component method I just draw the buffered image, so I am able to paint on the canvas from 'outside' by painting on the buffered image, which is public.

public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D)g; 
        g2.drawImage(buf, null, 0, 0); 

    }

Now there's a class RingChart which contains a "canvas":

public class RingChart extends JFrame{

    public Canvas c;
    ...

And I create a Graphics2D from the bufferedImage in the canvas class. This g2d is used for painting:

public RingChart(){
    c = new Canvas(1500,980,this);
    add(c);
    setSize(1500, 1000);
    setTitle("Hans");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    g2d = (Graphics2D)c.buf.createGraphics();
    ...

What I now was trying to achieve, was a mouse listener that listened to mouse events happening on the canvas. So when the user clicks on the canvas I could retrieve the position he clicked on, upon the canvas, through the event variable.

So I created a mouse listener:

class MouseHandler implements MouseListener {

    @Override
    public void mouseClicked(MouseEvent e){
        RingChart r = ((Canvas)e.getSource()).getParent();
        r.mouseClick(e);
    }
    ...

...and added this mouse listener to the canvas of the RingChart class (myChart is an instance of RingChart and c is the canvas it contains):

        ...
        MouseHandler mouse = new MouseHandler();
        myChart.c.addMouseListener(mouse);
        ...

But as I mentioned above, the mouse position, that's returned when the click event is called, does not seem to be accurate. I think the mistake must be somehow in the way I created that mouseListener or maybe assigned it to the wrong element or something like that. But I've tried quite a couple of things and it didn't change. Can maybe someone tell me, what I've done wrong?

UPDATE:

The code of the function "mouseClick" that is a member of RingChart and is called in the mouse listener:

public void mouseClick(MouseEvent evt){
    //evt = SwingUtilities.convertMouseEvent(this, evt, c);
    if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
        for(Element e : elements){
            if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
                //do some stuff
            }
        }
    }
}

Again, the hierarchy of my classes: RingChart --contains a--> Canvas --got a--> MouseListener. The shapes in this function are shapes that have been painted on the canvas c. Now I want to check, if the user has clicked on one of them. So as I thought, the shapes should be in canvas-coordinates and the event position should be in canvas-coordinates and everything should fit together. But it doesn't. Now user MadProgrammer told me, to use the ConvertMouseEvent function. But I currently don't see which exact way I should use this sensibly.

UPDATE:

I found a solution: All I had to do is adding the canvas not directly to the JFrame but to the ContentPane of the JFrame instead:

So instead:

public RingChart(){
    c = new Canvas(1500,980,this);
    add(c);
    ...

I do:

public RingChart(){
    c = new Canvas(1500,980,this);
    getContentPane().add(c);
    ...

Then I give the MouseListener to the ContentPane.

getContentPane().addMouseListener(new MouseHandler());
getContentPane().addMouseMotionListener(new MouseMoveHandler());

I don't know, if this is an elegant solution, but it works.


回答1:


The mouse event is automatically converted to be relative to the component that it occurred in that is, point 0x0 is always the top left corner of the component.

By using RingChart r = ((Canvas)e.getSource()).getParent(), you've effectively changed the reference, which now means the location is no longer valid.

You need to convert the location so that its coordinates are in the context of the parent component. Take a look at SwingUtilities.convertMouseEvent(Component, MouseEvent, Component)

UPDATE with PICTURES

Lets take this example...

The blue box has a relative position of 50px x 50px to the red box. If you click in the blue box, lets say at 25x25, the mouse coordinates will be relative to the blue box (0x0 will be the top left of the blue box).

If you then pass this event to the red box and try and use the coordinates from it, you will find that the coordinates will now be half way between the top left of the red box and the blue box, because the coordinates are context sensitive.

In order to get it to work, you need to translate the mouse events location from the blue box to the red box, which would make it 75x75

Now, I don't know what you're doing when you pass the mouse event to the RingChart so I'm only guessing that this is the issue you're facing.

UPDATED with Click Code

Okay, lets say, you have a Canvas at 100x100. You click on that Canvas at 50x50. You then pass that value back up the chain.

public void mouseClick(MouseEvent evt){
    //evt = SwingUtilities.convertMouseEvent(this, evt, c);
    if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
        for(Element e : elements){
            // Here, we are asking the shape if it contains the point 50x50...
            // Not 150x150 which would be the relative position of the click
            // in the context to the RingChart, which is where all your objects
            // are laid out.
            // So even the original Canvas you clicked on will return 
            // false because it's position + size (100x100x width x height) 
            // does not contain the specified point of 50x50...
            if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
                //do some stuff
            }
        }
    }
}

UPDATED

I think you have your references around the wrong way...

public static MouseEvent convertMouseEvent(Component source,
                       MouseEvent sourceEvent,
                       Component destination)

I think it should read something like

evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);

UPDATE with Code Example

Okay, so, I put this little example together...

public class TestMouseClickPoint extends JFrame {

    private ContentPane content;

    public TestMouseClickPoint() throws HeadlessException {

        setSize(600, 600);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        setLayout(new BorderLayout());

        content = new ContentPane();
        add(content);

    }

    protected void updateClickPoint(MouseEvent evt) {
        content.updateClickPoint(evt);
    }

    protected class ContentPane extends JPanel {

        private Point relativePoint;
        private Point absolutePoint;

        public ContentPane() {
            setPreferredSize(new Dimension(600, 600));
            setLayout(null); // For testing purpose only...

            MousePane mousePane = new MousePane();
            mousePane.setBounds(100, 100, 400, 400);

            add(mousePane);
        }

        protected void updateClickPoint(MouseEvent evt) {
            absolutePoint = new Point(evt.getPoint());
            evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);
            relativePoint = new Point(evt.getPoint());

            System.out.println(absolutePoint);
            System.out.println(relativePoint);

            repaint();
        }

        protected void paintCross(Graphics2D g2d, Point p) {
            g2d.drawLine(p.x - 5, p.y - 5, p.x + 5, p.y + 5);
            g2d.drawLine(p.x - 5, p.y + 5, p.x + 5, p.y - 5);
        }

        /*
         * This is not recommended, but I want to paint ontop of everything...
         */
        @Override
        public void paint(Graphics g) {
            super.paint(g);

            Graphics2D g2d = (Graphics2D) g;

            if (relativePoint != null) {
                g2d.setColor(Color.BLACK);
                paintCross(g2d, relativePoint);
            }

            if (absolutePoint != null) {
                g2d.setColor(Color.RED);
                paintCross(g2d, absolutePoint);
            }

        }
    }

    protected class MousePane extends JPanel {

        private Point clickPoint;

        public MousePane() {

            addMouseListener(new MouseAdapter() {

                @Override
                public void mouseClicked(MouseEvent e) {
                    clickPoint = e.getPoint();
                    TestMouseClickPoint.this.updateClickPoint(e);
                    repaint();
                }
            });

            setBorder(new LineBorder(Color.RED));

        }

        @Override
        protected void paintComponent(Graphics g) {

            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.BLUE);

            if (clickPoint != null) {
                g2d.drawLine(clickPoint.x, clickPoint.y - 5, clickPoint.x, clickPoint.y + 5);
                g2d.drawLine(clickPoint.x - 5, clickPoint.y, clickPoint.x + 5, clickPoint.y);
            }

        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException ex) {
        } catch (InstantiationException ex) {
        } catch (IllegalAccessException ex) {
        } catch (UnsupportedLookAndFeelException ex) {
        }

        new TestMouseClickPoint().setVisible(true);
    }
}

Basically, it will paint three points. The point that the mouse was clicked (relative to the source of the event), the unconverted point in the parent container and the converted point with the parent container.

The next thing you need to do is determine the mouse location is actually been converted, failing that. I'd probably need to see a working example of your code to determine what it is you're actually doing.



来源:https://stackoverflow.com/questions/12465003/java-mouseevent-position-is-inaccurate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!