Cómo agregar MouseListener al elemento en Java Swing Canvas

Me gustaría crear un panel Java que cree objetos donde el usuario haga clic. Como mi aplicación real utiliza un enfoque MVC, me gustaría también que estos objetos puedan volver a pintarse cuando se modifique un modelo, y proporcionar menús para cambiar sus propiedades.

Creo que la mejor manera de controlar sus ubicaciones xey sería tomar un enfoque basado en canvas por el cual JPanel llama a un método de dibujar sobre estos objetos desde el método paintComponent . Sin embargo, esto solo dibujará la forma en el canvas y no agregará al objeto mismo perdiendo todas las habilidades para controlar las propiedades del objeto. Estaría muy agradecido si alguien pudiera decirme cuál es el mejor enfoque para lo que quiero hacer.

He creado un código de muestra que se puede ver a continuación. Al hacer clic, me gustaría que el círculo cambie de color, que se implementa con un MouseListener (básicamente representa el cambio de las propiedades de los modelos en este pequeño ejemplo). Además, me gustaría asegurarme de que el acercamiento / alejamiento aún funciona con cualquier código / consejo de muestra que pueda proporcionar, así que agregué botones para acercar y alejar los objetos como una prueba rápida.

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

Como un lado intenté usar un JLayeredPane (útil porque también me gustaría JLayeredPane mis objetos) pero no pude hacer que mis objetos se renderizaran. Sé que no tiene un gestor de diseño predeterminado, así que intenté llamar a setBounds en el círculo del constructor, pero lamentablemente no funcionó. Sé que es mejor usar un administrador de diseño pero parece que no puedo encontrar uno adecuado para mis necesidades.

Gracias por adelantado.

No anule los componentes de paint , use paintComponent y no olvide llamar a super.paintComponent

Un componente ya tiene un concepto de “ubicación”, por lo que al pintar, la posición superior izquierda de su componente es en realidad 0x0

Lo que estás haciendo es en realidad pintar más allá de los límites de tu componente

Por ejemplo, si coloca su Circle a 100x100 y luego lo hizo …

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

En realidad, comenzarías a pintar a 200x200 (100 para la ubicación real del componente y 100 para tu posicionamiento adicional).

En su lugar use

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

Y vuelve e intenta usar JLayeredPane .

En realidad, podría escribir su propio administrador de diseño que tome la ubicación del componente y su tamaño preferido y actualice los límites de los componentes y luego aplicarlo a un JLayeredPane . Esto le brinda los “beneficios” de un diseño absoluto, pero lo mantiene dentro de cómo funciona Swing para actualizar sus componentes cuando cambian las cosas.

También debes tener cuidado con hacer algo como …

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

El contexto de Graphics es un recurso compartido. Eso significa que, todo lo que aplique, seguirá vigente cuando se pinta el siguiente componente. Esto puede producir algunos resultados extraños.

En cambio, intenta usar …

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

Actualizado

Para hacer zoom, echaría un vistazo más de cerca a JXLayer (o JLayer en Java 7)

El JXLayer (y las excelentes extensiones de PBar) se han ido bastante en la red, así que puedes tomar una copia de aquí

(Intenté encontrar un mejor ejemplo, pero esto es lo mejor que puedo hacer con el tiempo limitado que tengo disponible)

enter image description here

Actualizado con el ejemplo de zoom de trabajo

Cerca cerca

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

Dejé las sugerencias de renderizado para demostrar su uso, pero descubrí que se equivocaron al colocar el cursor dentro del campo de texto, pero es posible que desee jugar

Me gustaría agregar que arreglé el problema de acercamiento no de la manera sugerida por la respuesta, sino simplemente manteniendo la línea que aplicaba una llamada de transformación escalada en el método ExamplePanel ExamplePanel:

 g2.scale(zoom, zoom); 

Pensé que esta era la mejor implementación ya que ninguno de los componentes requiere ningún conocimiento sobre el zoom y parecía mucho más simple que JLayer ya que solo requería funcionalidades básicas de acercamiento.