Позиция Java MouseEvent неверна

У меня возникла проблема в Java с использованием созданного мной класса «холст», который является расширенным JPanel, для рисования анимированной кольцевой диаграммы. Эта диаграмма использует MouseListener для получения событий кликов.

Проблема в том, что положение мыши не кажется точным, то есть оно не похоже на «холст», а вместо этого относительно окна (в левом верхнем углу я получил около 30 пикселей для координаты y).

Это мой код:

Я создал класс, который расширяет JPanel и имеет BufferedImage в качестве члена.

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

В методе компонента рисования я просто рисую буферизованное изображение, поэтому я могу рисовать на холсте «снаружи», рисуя на буферизованном изображении, которое общедоступно.

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

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

    }

Теперь есть класс RingChart, который содержит «холст»:

public class RingChart extends JFrame{

    public Canvas c;
    ...

И я создаю Graphics2D из буферизованного изображения в классе холста. Этот g2d используется для рисования:

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

Чего я сейчас пытался добиться, так это прослушивателя мыши, который прослушивал события мыши, происходящие на холсте. Поэтому, когда пользователь нажимает на холст, я могу получить позицию, на которую он нажал на холсте, через переменную события.

Итак, я создал прослушиватель мыши:

class MouseHandler implements MouseListener {

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

... и добавил этот прослушиватель мыши на холст класса RingChart (myChart является экземпляром RingChart, а c - это холст, который он содержит):

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

Но, как я упоминал выше, позиция мыши, возвращаемая при вызове события щелчка, кажется неточной. Я думаю, что ошибка должна быть как-то связана с тем, как я создал этот mouseListener или, возможно, назначил его не тому элементу или что-то в этом роде. Но я пробовал довольно много вещей, и это не изменилось. Может быть, кто-нибудь подскажет, что я сделал не так?

ОБНОВИТЬ:

Код функции «mouseClick», которая является членом RingChart и вызывается в прослушивателе мыши:

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

Опять же, иерархия моих классов: RingChart --содержит --> Canvas -- получил --> MouseListener. Фигуры в этой функции — это фигуры, которые были нарисованы на холсте c. Теперь я хочу проверить, нажал ли пользователь на один из них. Итак, как я и думал, фигуры должны быть в координатах холста, а положение события должно быть в координатах холста, и все должно совпадать. Но это не так. Теперь пользователь MadProgrammer сказал мне использовать функцию ConvertMouseEvent. Но в настоящее время я не вижу, как именно я должен использовать это разумно.

ОБНОВИТЬ:

Я нашел решение: все, что мне нужно было сделать, это добавить холст не напрямую к JFrame, а к ContentPane вместо JFrame:

Итак, вместо этого:

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

I do:

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

Затем я даю MouseListener ContentPane.

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

Я не знаю, элегантное ли это решение, но оно работает.


person bweber    schedule 17.09.2012    source источник
comment
e.getSource().getMousePosition() должен обеспечивать правильное относительное положение мыши.   -  person obataku    schedule 17.09.2012
comment
Спасибо за этот совет, я попробую... РЕДАКТИРОВАТЬ: Ну, похоже, это ничего не меняет в этом поведении...   -  person bweber    schedule 17.09.2012
comment
Если я добавлю g2d.fillOval((int)c.getMousePosition().getX(), (int)c.getMousePosition().getY(), 5, 5); для метода рисования кажется, что нарисованный овал находится не прямо там, где указывает указатель, а немного ниже. Так что, похоже, что-то еще не так.   -  person bweber    schedule 17.09.2012
comment
Овал — не лучшая форма для использования (для начала, он нарисован не вокруг своего центра, а в верхнем левом углу). Вместо этого нарисуйте перекрестие, используя g2d.drawLine((int)c.getMousePosition().getX() - 5, (int)c.getMousePosition().getY(), (int)c.getMousePosition().getX() + 5, (int)c.getMousePosition().getY()); и g2d.drawLine((int)c.getMousePosition().getX(), (int)c.getMousePosition().getY() - 5, (int)c.getMousePosition().getX(), (int)c.getMousePosition().getY() + 5);, чтобы было легче увидеть, где происходит событие мыши.   -  person Bobulous    schedule 18.09.2012
comment
Ну, я знаю, что овал рисуется из левого, верхнего угла. Но в любом случае это явно не в том месте.   -  person bweber    schedule 18.09.2012


Ответы (1)


Событие мыши автоматически преобразуется относительно компонента, в котором оно произошло, то есть точка 0x0 всегда является левым верхним углом компонента.

Используя RingChart r = ((Canvas)e.getSource()).getParent(), вы фактически изменили ссылку, что теперь означает, что местоположение больше недействительно.

Вам нужно преобразовать местоположение, чтобы его координаты были в контексте родительского компонента. Взгляните на SwingUtilities.convertMouseEvent(Component, MouseEvent, Component)

ОБНОВЛЕНИЕ с ИЗОБРАЖЕНИЯМИ

Возьмем этот пример...

Образец

Синий прямоугольник имеет относительную позицию 50 x 50 пикселей относительно красного прямоугольника. Если щелкнуть синее поле, скажем, 25x25, координаты мыши будут относительно синего поля (0x0 будет верхним левым краем синего поля).

Если вы затем передадите это событие в красное поле и попытаетесь использовать координаты из него, вы обнаружите, что координаты теперь будут на полпути между верхним левым краем красного поля и синим полем, потому что координаты зависят от контекста.

Чтобы заставить его работать, вам нужно перевести местоположение событий мыши из синего поля в красное поле, что сделает его 75x75.

Теперь я не знаю, что вы делаете, когда передаете событие мыши в RingChart, поэтому я только предполагаю, что это проблема, с которой вы столкнулись.

ОБНОВЛЕНО с помощью Click Code

Хорошо, допустим, у вас есть Canvas с разрешением 100 x 100. Вы нажимаете на этот Canvas размером 50x50. Затем вы передаете это значение обратно по цепочке.

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

ОБНОВЛЕНО

Я думаю, что вы неправильно ориентируетесь...

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

Я думаю, что это должно читаться как-то так

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

ОБНОВЛЕНИЕ с примером кода

Итак, я собрал этот небольшой пример...

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

В основном, это будет рисовать три точки. Точка щелчка мыши (относительно источника события), непреобразованная точка в родительском контейнере и преобразованная точка в родительском контейнере.

введите здесь описание изображения

Следующее, что вам нужно сделать, это определить, что местоположение мыши действительно было преобразовано, если это не удалось. Мне, вероятно, нужно увидеть рабочий пример вашего кода, чтобы определить, что вы на самом деле делаете.

person MadProgrammer    schedule 17.09.2012
comment
Ну, я не совсем понимаю. Я только что попытался получить родителя холста, которым является RingChart, поэтому я могу вызвать на нем функцию. Но позиция по-прежнему должна быть относительно холста (это то место, где я рисую, и это объект, у которого есть прослушиватель мыши и для которого вызывается событие мыши). - person bweber; 18.09.2012
comment
Нет, не будет. Местоположение мыши будет относиться к источнику события (e.getSource()). Если вы хотите использовать эти координаты в родительском контейнере источника, вам необходимо соответствующим образом преобразовать координаты. - person MadProgrammer; 18.09.2012
comment
Хорошо, я думаю, что в основном понимаю, что вы имеете в виду, но я не уверен, применимо ли это к моему случаю / я не знаю, как это использовать здесь. Я добавил функцию mouseClick выше, возможно, вы сможете взглянуть на нее. - person bweber; 18.09.2012
comment
Да, я понимаю, но это все равно не работает ;) Но как вы предлагаете мне использовать функцию convertMouseEvent? Как я написал в примере? evt = SwingUtilities.convertMouseEvent(this, evt, c); Это ничего не меняет (это кольцевая диаграмма, c - холст). - person bweber; 18.09.2012
comment
Я думаю, что вы неправильно указали параметры, проверьте обновление - person MadProgrammer; 19.09.2012
comment
Что ж, извините, но это вообще ничего не меняет. - person bweber; 19.09.2012
comment
Я нашел решение. Проверьте обновление. Спасибо за вашу помощь. - person bweber; 19.09.2012