2014-01-16 91 views
1

我想創建一個Java面板,該面板創建用戶點擊的對象。由於我的實際應用程序使用MVC方法,我還希望這些對象能夠在模型更改時重新繪製自己,並提供用於更改其屬性的菜單。如何將MouseListener添加到Java Swing Canvas上的項目

我認爲控制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,但遺憾的是它沒有工作。我知道使用佈局管理器更好,但似乎無法找到適合我需求的佈局管理器!

在此先感謝。

回答

6

不要覆蓋paint組件,使用paintComponent不要忘記調用super.paintComponent

的組件已擁有「地利」的概念,所以畫的時候,你的組件的左上角位置實際上是0x0

你在做什麼其實是繪畫超越了你部分

例如邊界,如果你把你的Circle100x100,然後做...

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中)

的JXLayer(和優秀PBar擴展)已經在網上相當了,所以你可以從here

(我試過找到一個更好的例子,但是這是我能在有限的時間,我有)

enter image description here

更新與工作縮放例如

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


     } 

    } 

} 

我做的最好已經留下了渲染提示來演示它們的用法,但是我發現它們在文本字段中將光標的位置擰緊了,但是您可能想要已經播放

0

我只是想,只是通過保持應用縮放在ExamplePanel的paintComponent方法變換調用的行補充說,我固定的縮放問題不是由答覆建議的方式:

g2.scale(zoom, zoom); 

我認爲這是最好的實現,因爲沒有任何組件需要關於縮放的任何知識,並且它似乎比JLayer簡單得多,因爲我只需要基本的縮放功能。

+0

問題是,這不會轉化鼠標事件,這是'JLayer'將提供什麼,因此推薦它的原因。還有其他問題。父容器實際上不必是繪畫過程的一部分,可以繪製子組件,而無需調用父容器的繪畫,跳過縮放設置... – MadProgrammer

+0

啊,你是對的,雖然我一直在玩與'TestWrapped'演示通過改變它來放大整個頁面。它似乎也沒有正確地翻譯鼠標事件。例如,當在頁面上繪圖時,它不是我的光標所在的位置。 –

+0

我用一個可運行的示例更新了我的問題 – MadProgrammer

相關問題