2011-11-24 347 views
3

在我的應用程序中,我需要繪製像Photoshop一樣的網格線 - 例如,用戶可以在文檔上拖動線條以幫助對齊圖層。現在,問題是我能夠繪製這樣的線條(這只是簡單的使用Line2D的簡單Java2D繪畫),但是我無法將這些線條保留在其他任何東西之上,因爲當子組件繪製自己時,我的網格線被擦除。在所有其他組件上繪製(Swing,Java)

程序結構是這樣的:的JFrame - >的JPanel - > JScrollPane中 - >的JPanel - > [許多其他JPanels,它們是類似的層]

作爲測試,我添加繪製代碼的JFrame,它能正確顯示我的Line2D實例在其他任何位置上。但是,當我在需要該子進行重繪的子組件中執行任何操作時,JFrame中繪製的線將被刪除。

我知道這是預期的擺動行爲 - 也就是說,它只會重繪那些已經改變的區域。但是,我正在尋找一種方法,不斷在其他所有方面上繪製網格線。

我能夠使它工作的唯一方法是使用一個Swing Timer,它每10ms在我的根組件上調用repaint(),但它消耗了大量的CPU。

UPDATE
工作的例子的代碼如下。請注意,在我的真實應用程序中,我有幾十個可能觸發重繪(repaint)的不同組件,它們都沒有引用網格線繪製的組件(當然,我可以將它傳遞給每個人)是最新的選項)

import java.awt.BasicStroke; 
import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.geom.Line2D; 

import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 

public class GridTest extends JFrame { 
    public static void main(String[] args) { 
     new GridTest().run(); 
    } 

    private void run() { 
     setLayout(null); 
     setPreferredSize(new Dimension(200, 200)); 
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     final JPanel p = new JPanel(); 
     p.setBounds(20, 20, 100, 100); 
     p.setBackground(Color.white); 
     add(p); 

     JButton b = new JButton("Refresh"); 
     b.addActionListener(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       // When I call repaint() here, the paint() method of 
       // JFrame it's not called, thus resulting in part of the 
       // red line to be erased/overridden. 

       // In my real application application, I don't have 
       // easy access to the component that draws the lines 
       p.repaint(); 
      } 
     }); 
     b.setBounds(0, 150, 100, 30); 
     add(b); 

     pack(); 
     setVisible(true); 
    } 

    @Override 
    public void paint(Graphics g) { 
     super.paint(g); 

     Graphics2D gg = (Graphics2D)g.create(); 
     Line2D line = new Line2D.Double(0, 50, getWidth(), 50); 
     gg.setStroke(new BasicStroke(3)); 
     gg.setColor(Color.red); 
     gg.draw(line); 
     gg.dispose(); 
    } 
} 
+3

爲什麼不能在的paintComponent方法,而不是開始的結束畫網格線?另外,要獲得更具體的幫助,請考慮創建併發布顯示問題的[SSCCE](http://sscce.org)。 –

+0

我這樣做了,但問題是,當一個子組件重新繪製自己時,父組件(位於層次結構中最遠端的組件,如果沒有未知的getParent()調用)不能訪問它。我會嘗試獲得一個工作代碼在這裏展示。 –

+0

@Rafael Steil請參閱我的編輯 – mKorbel

回答

5

,如果你想畫在JComponents放置到JScrollPane,那麼你可以畫到JViewPort,例如here

編輯:

1)怎麼一回事,因爲你的代碼畫到錯誤的容器,到JFrame,一定可以畫到JFrame,但你必須提取RootPane or GlassPane

2),你必須學會​​如何LayoutManagers的作品,我讓跟原來的大小調整,不是好和非常壞的代碼

3 )油漆到GlassPaneJViewPort

import java.awt.*; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.geom.Line2D; 
import javax.swing.*; 

public class GridTest extends JFrame { 
    private static final long serialVersionUID = 1L; 

    public static void main(String[] args) { 
     new GridTest().run(); 
    } 

    private void run() { 
     setLayout(null); 
     setPreferredSize(new Dimension(200, 200)); 
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     final JPanel p = new JPanel() { 
      private static final long serialVersionUID = 1L; 

      @Override 
      public void paint(Graphics g) { 

       super.paint(g); 
       Graphics2D gg = (Graphics2D) g.create(); 
       Line2D line = new Line2D.Double(0, 50, getWidth(), 50); 
       gg.setStroke(new BasicStroke(3)); 
       gg.setColor(Color.red); 
       gg.draw(line); 
       //gg.dispose(); 

      } 
     }; 
     p.setBounds(20, 20, 100, 100); 
     p.setBackground(Color.white); 
     add(p); 

     JButton b = new JButton("Refresh"); 
     b.addActionListener(new ActionListener() { 

      @Override 
      public void actionPerformed(ActionEvent e) { 
       p.repaint(); 
      } 
     }); 
     b.setBounds(0, 150, 100, 30); 
     add(b); 

     pack(); 
     setVisible(true); 
    } 
} 

編輯:2,如果你希望單行線,在固定的界限

enter image description here enter image description here enter image description here

import java.awt.*; 
import java.awt.event.*; 
import java.awt.geom.Line2D; 
import javax.swing.*; 
import javax.swing.border.LineBorder; 

public class GridTest extends JFrame { 
    private static final long serialVersionUID = 1L; 

    public static void main(String[] args) { 
     new GridTest().run(); 
    } 

    private void run() { 
     setPreferredSize(new Dimension(200, 200)); 
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     final JPanel p = new JPanel() { 
      private static final long serialVersionUID = 1L; 

      @Override 
      public void paint(Graphics g) { 
       super.paint(g); 
       Graphics2D gg = (Graphics2D) g.create(); 
       Line2D line = new Line2D.Double(0, 50, getWidth(), 50); 
       gg.setStroke(new BasicStroke(3)); 
       gg.setColor(Color.red); 
       gg.draw(line); 
       gg.dispose(); 
      } 
     }; 
     JPanel p1 = new JPanel(); 
     p1.setBorder(new LineBorder(Color.black,1)); 
     JPanel p2 = new JPanel(); 
     p2.setBorder(new LineBorder(Color.black,1)); 
     JPanel p3 = new JPanel(); 
     p3.setBorder(new LineBorder(Color.black,1)); 
     p.setLayout(new GridLayout(3,0)); 
     p.add(p1); 
     p.add(p2); 
     p.add(p3); 
     p.setBounds(20, 20, 100, 100); 
     p.setBackground(Color.white); 
     add(p, BorderLayout.CENTER); 

     JButton b = new JButton("Refresh"); 
     b.addActionListener(new ActionListener() { 

      @Override 
      public void actionPerformed(ActionEvent e) { 
       p.repaint(); 
      } 
     }); 
     add(b, BorderLayout.SOUTH); 

     pack(); 
     setVisible(true); 
    } 
} 
+0

我試過了,但是(至少在我的測試中),當滾動窗格的子組件重新繪製自己時,viewPort的stageChanged(ChangeListener)不會被調用(只有當我執行其他操作時才使用滾動條)。如果我的孩子的組件被固定在他們的位置,它會工作,但問題是,用戶可能拖曳他們在繪製區 –

+0

@Rafael Steil那非常理論的問題,請張貼代碼在http://sscce.org/形式明確你的問題 – mKorbel

+0

好的,我創建了一個非常小而簡單的例子來說明問題。 –

1

假設父框架已經有了所有它繪製網格線的列表,你可以做的就是讓每個孩子幀繪製的線條自己的個人位。在僞代碼:

gridlines = getParentsGridLines() 
gridlines.offsetBasedOnRelativePosition() 
drawStuff() 
4

一個可行的辦法是重寫JPanel的repaint方法,以便它調用的contentPane的重繪方法來代替。另一點是,你可能不應該直接在JFrame中繪製網格線,而應該在其contentPane中繪製網格線。與我通常推薦的方法相反,我認爲你最好重寫contentPane的paint方法(或其他包含JPanel的方法),而不是paintComponent方法,以便在子對象被繪完後調用。例如:

import java.awt.BasicStroke; 
import java.awt.BorderLayout; 
import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.FlowLayout; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.Stroke; 
import java.awt.event.ActionEvent; 

import javax.swing.*; 

@SuppressWarnings("serial") 
public class GridTest2 extends JPanel { 
    private static final Stroke LINE_STROKE = new BasicStroke(3f); 
    private boolean drawInPaintComponent = false; 

    public GridTest2() { 
     final JPanel panel = new JPanel() { 
     @Override 
     public void repaint() { 
      JRootPane rootPane = SwingUtilities.getRootPane(this); 
      if (rootPane != null) { 
       JPanel contentPane = (JPanel) rootPane.getContentPane(); 
       contentPane.repaint(); 
      } 
     } 
     }; 
     panel.setBackground(Color.white); 
     panel.setPreferredSize(new Dimension(100, 100)); 

     JPanel biggerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); 
     biggerPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 0, 0)); 
     biggerPanel.setOpaque(false); 
     biggerPanel.add(panel); 

     JButton resetButton = new JButton(new AbstractAction("Reset") { 
     public void actionPerformed(ActionEvent arg0) { 
      panel.repaint(); 
     } 
     }); 
     JPanel btnPanel = new JPanel(); 
     btnPanel.add(resetButton); 

     setLayout(new BorderLayout()); 
     add(biggerPanel, BorderLayout.CENTER); 
     add(btnPanel, BorderLayout.SOUTH); 
    } 

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

    @Override 
    protected void paintComponent(Graphics g) { 
     super.paintComponent(g); 
     if (drawInPaintComponent) { 
     drawRedLine(g); 
     } 
    } 

    @Override 
    public void paint(Graphics g) { 
     super.paint(g); 
     if (!drawInPaintComponent) { 
     drawRedLine(g); 
     } 
    } 

    private void drawRedLine(Graphics g) { 
     Graphics2D g2 = (Graphics2D) g; 
     g2.setStroke(LINE_STROKE); 
     g2.setColor(Color.red); 
     g2.drawLine(0, 50, getWidth(), 50); 
    } 

    private static void createAndShowGui() { 
     GridTest2 mainPanel = new GridTest2(); 

     JFrame frame = new JFrame("GridTest2"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.getContentPane().add(mainPanel); 
     frame.pack(); 
     frame.setLocationByPlatform(true); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      createAndShowGui(); 
     } 
     }); 
    } 
} 
1

Swing在JFrames(和類似組件)中使用JLayeredPane。使用分層窗格,您可以將純繪製組件放在主內容上。

This code使用放置在的JLayeredPane內定位(和自動重畫)任意裝飾品主內容的任何部件的上方,從而不需要覆蓋任何給定組分的塗料()方法的組件。

0

我知道這是舊的文章,但我已經最近同樣的問題...
你應該重寫paintChildren而不是paintpaintComponent。 從JComponent.paint documentation

援引擺動,以繪製組件。應用程序不應該直接調用paint,而應該使用repaint方法來調度組件以進行重繪。
此方法實際上將繪畫作品委託給三個受保護的方法:paintComponent,paintBorder和paintChildren。 以列出的順序調用它們以確保兒童出現在組件本身之上。一般而言,組件及其子女不應在分配給邊界的插入區域上繪畫。一如以往,子類可以重寫此方法。只想要專門化UI(外觀)委託的繪畫方法的子類應該重寫paintComponent。

所以,如果你

@Override 
protected void paintChildren(Graphics g){ 
    super.paintChildren(g); 
    paintGrid(g); 
} 

電網將在你的子組件的頂部^^