如何将MouseListener添加到Java Swing Canvas上的项目
我想创build一个Java面板,在用户点击的地方创build对象。 由于我的实际应用程序使用MVC方法,我也希望这些对象能够在模型更改时重新绘制自己,并提供菜单来更改它们的属性。
我认为控制x和y位置的最好方法是采用基于canvas的方法, JPanel
从paintComponent
方法调用这些对象的绘制方法。 然而,这只会在canvas上绘制形状,并不会添加对象本身,从而失去控制对象属性的所有function。 如果有人能告诉我我想要做的最好的方法,我将非常感激。
我已经创build了一些示例代码,可以在下面看到。 点击时,我想圆圈改变颜色,这是使用MouseListener实现的(它基本上代表了改变这个小例子中的模型属性)。 此外,我只是想确保放大/缩小仍然可以使用任何示例代码/build议可以提供,所以我已经添加button来放大和缩小对象进行快速testing。
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
(有用,因为我也想分层我的对象),但无法让我的对象甚至渲染。 我知道它没有默认的布局pipe理器,所以试图在构造函数的圆圈中调用setBounds
,但可悲的是它没有工作。 我知道最好是使用布局pipe理器,但似乎无法find适合我需求的布局pipe理器!
提前致谢。
不要重写paint
组件,使用paintComponent
,不要忘记调用super.paintComponent
一个组件已经有一个“位置”的概念,所以在绘制时,组件的左上angular位置实际上是0x0
你正在做的是实际上绘制超出你的界限组件
例如,如果你把你的Circle
放在100x100
,然后做…
g2.fillOval(x, y, DIMENSION, DIMENSION);
你实际上开始绘画在200x200
(100为组件的实际位置和100为您额外的定位)。
反而使用
g2.fillOval(x, y, DIMENSION, DIMENSION);
回去尝试使用JLayeredPane
。
您实际上可以编写自己的布局pipe理器,它可以获取组件的位置以及它的首选大小并更新组件边界,然后将其应用于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扩展)已经在网上相当了,所以你可以从这里抓取一个副本
(我试着find一个更好的例子,但是这是我可以利用有限的时间做的最好的例子)
更新与工作缩放例子
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); } } }
我已经留下了渲染提示来演示它们的用法,但是我发现它们在文本字段中将光标定位,但是您可能想要播放
我只想补充一点,我解决了缩放问题,而不是按照答案build议的方式,只是通过在ExamplePanel
paintComponent方法中保留应用了缩放转换调用的ExamplePanel
:
g2.scale(zoom, zoom);
我认为这是最好的实现,因为没有任何组件需要任何有关缩放的知识,它似乎比JLayer
简单,因为我只需要基本的缩放function。