【问题标题】:How to add MouseListener to item on Java Swing Canvas如何将 MouseListener 添加到 Java Swing Canvas 上的项目
【发布时间】:2014-02-06 03:21:33
【问题描述】:

我想制作一个 Java 面板,用于创建用户单击的对象。由于我的实际应用程序使用 MVC 方法,我还希望这些对象能够在模型更改时重新绘制自己,并提供菜单来更改它们的属性。

我认为控制它们的 x 和 y 位置的最佳方法是采用基于画布的方法,其中 JPanelpaintComponent 方法调用这些对象的绘制方法。然而,这只会在画布上绘制形状,并且不会添加对象本身,从而失去控制对象属性的所有能力。如果有人能告诉我我想做的最好的方法,我将不胜感激。

我创建了一些示例代码,如下所示。单击时,我希望圆圈更改颜色,这是使用 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,但遗憾的是它不起作用。我知道最好使用布局管理器,但似乎找不到适合我需要的!

提前致谢。

【问题讨论】:

    标签: java swing canvas position


    【解决方案1】:

    不要覆盖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(或 Java 7 中的 JLayer)

    JXLayer(和优秀的 PBar 扩展)已经在网上大放异彩,因此您可以从 here 获取副本

    (我试图找到一个更好的例子,但这是我在有限的时间所能做的最好的)

    更新了工作缩放示例

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

    我已经留下了渲染提示来演示它们的使用,但我发现它们与光标在文本字段中的定位搞砸了,但你可能想玩一下

    【讨论】:

      【解决方案2】:

      我想补充一点,我不是按照答案建议的方式解决了缩放问题,而是通过在ExamplePanelpaintComponent 方法中保留应用缩放变换调用的行:

      g2.scale(zoom, zoom);
      

      我认为这是最好的实现,因为所有组件都不需要任何有关缩放的知识,而且它似乎比 JLayer 简单得多,因为我只需要基本的缩放功能。

      【讨论】:

      • 问题是,这不会翻译鼠标事件,这是JLayer 将提供的,因此推荐它的原因。还有其他问题。父容器实际上不必是绘制过程的一部分,子组件可以在不调用父容器的绘制、跳过缩放设置的情况下绘制......
      • 啊,你是对的,虽然我一直在玩TestWrapped 演示,将其更改为放大整个页面。它似乎也不能正确翻译鼠标事件。例如,在页面上绘图时,它不在我的光标所在的位置。
      猜你喜欢
      • 1970-01-01
      • 2015-09-04
      • 2013-06-29
      • 1970-01-01
      • 1970-01-01
      • 2011-02-18
      • 2013-02-08
      • 2015-02-04
      相关资源
      最近更新 更多