Как добавить MouseListener к элементу на Java Swing Canvas

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

Я думаю, что лучший способ контролировать их координаты x и y - использовать холст, основанный на JPanel вызывает метод рисования на этих объектах из paintComponent метод. Это, однако, только нарисует фигуру на холсте и не добавляет сам объект, теряя все возможности для управления свойствами объекта. Я был бы очень признателен, если бы кто-то мог сказать мне лучший подход к тому, что я хочу сделать.

Я создал пример кода, который можно увидеть ниже. При нажатии я бы хотел, чтобы круг изменил цвет, что реализовано с помощью MouseListener (в основном это представляет изменение свойств моделей в этом небольшом примере). Также я просто хотел бы убедиться, что увеличение / уменьшение по-прежнему работает с любым примером кода / рекомендации, поэтому я добавил кнопки для увеличения и уменьшения объектов в качестве быстрого теста.

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.geom.Ellipse2D;

public class Main  {

    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                ExamplePanel panel = new ExamplePanel();

                frame.add(panel);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

    //I could not get this to with when it extended JLayeredPane
    private static class ExamplePanel extends JPanel {
        private static final int maxX = 500;
        private static final int maxY = 500;
        private static double zoom = 1;
        private static final Circle circle = new Circle(100, 100);

        public ExamplePanel() {
            this.setPreferredSize(new Dimension(maxX, maxY));
            this.setFocusable(true);

            Button zoomIn = new Button("Zoom In");
            zoomIn.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    zoom += 0.1;
                    repaint();
                }
            });
            add(zoomIn);

            Button zoomOut = new Button("Zoom Out");
            zoomOut.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    zoom -= 0.1;
                    repaint();
                }
            });
            add(zoomOut);


            // add(circle); // Comment back in if using JLayeredPane
        }

        @Override
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            g2.scale(zoom, zoom);
            super.paintComponent(g);

            circle.paint(g); // Comment out if using JLayeredPane
        }

    }

    static class Circle extends JPanel {
        private Color color = Color.RED;
        private final int x;
        private final int y;
        private static final int DIMENSION = 100;

        public Circle(int x, int y) {
            // setBounds(x, y, DIMENSION, DIMENSION);
            this.x = x;
            this.y = y;
            addMouseListener(new MouseAdapter() {

                @Override
                public void mousePressed(MouseEvent e) {
                    color = Color.BLUE;
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                }
            });
        }

        public void paint(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setPaint(color);
            g2.fillOval(x, y, DIMENSION, DIMENSION); 
        }

        // I had some trouble getting this to work with JLayeredPane even when setting the bounds
        // In the constructor
        // @Override
        // public void paintComponent(Graphics g) {
        //     Graphics2D g2 = (Graphics2D) g;
        //     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        //     g2.setPaint(color);
        //     g2.fillOval(x, y, DIMENSION, DIMENSION); 
        // }

        @Override
        public  Dimension getPreferredSize(){
            return new Dimension(DIMENSION, DIMENSION);
        }
    }
}

Кроме того, я попытался использовать JLayeredPane(полезно, потому что я также хотел бы наложить свои объекты на слои), но не мог заставить мои объекты даже визуализировать. Я знаю, что у него нет менеджера по умолчанию, поэтому попытался вызвать setBounds в кругу в конструкторе, но, к сожалению, это не сработало. Я знаю, что лучше использовать менеджер макетов, но не могу найти подходящего для моих нужд!

Заранее спасибо.

2 ответа

Решение

Не переопределять paint компоненты, использование paintComponent и не забудьте позвонить super.paintComponent

Компонент уже имеет понятие "местоположение", поэтому при рисовании верхняя левая позиция вашего компонента фактически 0x0

То, что вы делаете, на самом деле рисует за пределами вашего компонента

Например, если вы разместите свой Circle в 100x100 а потом сделал...

g2.fillOval(x, y, DIMENSION, DIMENSION); 

Вы бы действительно начали рисовать в 200x200 (100 для фактического местоположения компонента и 100 для вас дополнительного позиционирования).

Вместо этого используйте

g2.fillOval(x, y, DIMENSION, DIMENSION); 

И вернитесь и попробуйте использовать JLayeredPane,

Вы могли бы написать свой собственный менеджер компоновки, который принимает местоположение компонента и его предпочтительный размер и обновляет границы компонентов, а затем применяет это к JLayeredPane, Это дает вам "преимущества" абсолютного макета, но не дает вам понять, как Swing работает над обновлением своих компонентов, когда что-то меняется.

Вы также должны быть осторожны, делая что-то вроде...

Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

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

Вместо этого попробуйте использовать...

Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//...
g2.dispose();

обновленный

Для увеличения я бы поближе посмотрел на JXLayer (или JLayer в Java 7)

The JXLayer (and excellent PBar extensions) have gone quite on the net, so you can grab a copy from here

(I tried finding a better example, but this is the best I could do with the limited time I have available)

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

Updated with working zooming example

ZoomZoom

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;

public class TestJLayerZoom {

    public static void main(String[] args) {
        new TestJLayerZoom();
    }

    public TestJLayerZoom() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JXLayer<JComponent> layer;
        private DefaultTransformModel transformModel;
        private JPanel content;

        public TestPane() {

            content = new JPanel(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridy = 0;

            JLabel label = new JLabel("Hello");
            JTextField field = new JTextField("World", 20);

            content.add(label, gbc);
            content.add(field, gbc);

            gbc.gridy++;
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            final JSlider slider = new JSlider(50, 200);
            slider.addChangeListener(new ChangeListener() {

                @Override
                public void stateChanged(ChangeEvent e) {
                    int value = slider.getValue();
                    double scale = value / 100d;
                    transformModel.setScale(scale);
                }
            });
            content.add(slider, gbc);

            transformModel = new DefaultTransformModel();
            transformModel.setScaleToPreferredSize(true);

            Map<RenderingHints.Key, Object> hints = new HashMap<>();
            //hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            //hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            //hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            layer = TransformUtils.createTransformJXLayer(content, transformModel, hints);
            setLayout(new BorderLayout());
            add(layer);


        }

    }

}

I've left the rendering hints in to demonstrate their use, but I found that they screwed with the positing of the cursor within the text field, but you might like to have a play

Я просто хотел бы добавить, что я исправил проблему масштабирования не так, как предложено в ответе, а просто сохранил строку, которая применила масштабированный вызов преобразования в ExamplePanel метод paintComponent:

g2.scale(zoom, zoom);

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

Другие вопросы по тегам