2013-08-05 60 views
3

有這篇文章:EDT隊列切割


有人插隊! 時不時會發生一些揮杆事件在事件隊列中以不正確的順序處理(並且沒有任何東西會讓我的血液沸騰,就像有人切入隊列一樣)導致奇怪的行爲。這是最好的一個小代碼片段。閱讀下面的代碼片段,仔細想想你想象事件發生的順序。

button.addActionListener(new ActionListener() { 
    public void actionPerformed(ActionEvent arg0) { 
     repaint(); 
     doSomething(); 
    } 
}); 

大多數開發人員會想象,repaint()方法將導致繪製操作在doSomething()方法調用之前發生。但實際上並非如此,調用repaint()將創建一個新的繪圖事件,該事件將添加到事件隊列的末尾。這個新的繪畫事件將僅在當前的行動事件完成後處理(派發)。這意味着doSomething()方法將在調度隊列上的新Paint事件之前執行。

這裏的關鍵是調用repaint()將創建一個新的paint事件,該事件將被添加到結束事件隊列中,而不是立即處理。這意味着沒有任何事件會跳過隊列(並且我的血液可以保持在正確的溫度)。

(source)


我的問題是,我怎麼能強迫的Swing做repaint();之前doSomething();

此外,如果在doSomething();之內有repaint()方法的調用,則只有在doSomething();完成後纔會執行它們。有沒有辦法讓我暫停doSomething(); mid-executin,然後投入reapaint();,完成它,然後恢復doSomething();

只有我已經找到了解決辦法,到目前爲止是this(link),但它不是真正實用...

+2

與往常一樣的問題(你知道我仍然認爲你正在做一些根本性的事情 - 當組件被繪製時,應該無所謂,有時候 - 改變後應該足夠好;-) - 爲什麼? – kleopatra

+0

不會減少你只丟失了所有事件,方法和通知, – mKorbel

回答

2

重繪()增加了一個新的油漆請求事件指派線程(EDT)的隊列的末尾。因此在doSomething()內撥打repaint()的幾個電話只會在doSomething()完成後重新繪製。 (我假設doSomething()始終是從EDT調用的,你的代碼示例調用actionPerformed內部的doSomething(),它始終由EDT調用。)

paint()請求排隊的原因是減少次數一個組件被繪製。排隊repaint()請求允許多種方法將不同的組件標記爲髒,以便在一次昂貴的paint()操作中可以同時重新繪製它們。

如果您確實想要立即強制重繪,則有像paintImmediately(Rectangle r)paintImmediately(int x, int y,int w,int h)這樣的方法,但您必須知道要重繪的尺寸。

如果您有對Swing組件正在使用的當前Graphics的引用,則您也可以始終自己調用paint(Graphics g)。 (您也可以使用這種技術從Image對象創建您自己的圖形對象,如果您想截取屏幕截圖並將其寫入圖片文件)。

+0

paintImmediately RepailedManager引起的awfull異常(當前JVM實例可以是TaskManager的addept),如果EDT不爲空,是必需的,必須測試,並且只有在isEventDispatchThread返回false – mKorbel

6

那麼,你和所引用文章的作者在這裏沒有提到這一點。「重繪」方法調用簡單地通知重繪經理認爲:

  1. 有重繪組件(您所說的「重繪」)
  2. 它應該被重新粉刷(X,Y,W,H)夾(如果你稱之爲「重繪」 W/O指定RECT - 它會在整個組件範圍,(0,0,W,H))

所以這其實並不重要重繪將會發生,因爲如果你爲同一個組件調用一大堆重新繪製的話,你甚至可能不會注意到它。這也是爲什麼這樣的後續調用可能會合並。檢查這個例子:

private static int repaintCount = 0; 

public static void main (String[] args) 
{ 
    final JComponent component = new JComponent() 
    { 

     protected void paintComponent (Graphics g) 
     { 
      try 
      { 
       // Simulate heavy painting method (10 milliseconds is more than enough) 
       Thread.sleep (10); 
      } 
      catch (InterruptedException e) 
      { 
       e.printStackTrace(); 
      } 

      g.setColor (Color.BLACK); 
      g.drawLine (0, 0, getWidth(), getHeight()); 

      repaintCount++; 
      System.out.println (repaintCount); 
     } 
    }; 
    component.setPreferredSize (new Dimension (200, 200)); 

    JFrame frame = new JFrame(); 
    frame.add (component); 
    frame.pack(); 
    frame.setLocationRelativeTo (null); 
    frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); 
    frame.setVisible (true); 

    new Thread (new Runnable() 
    { 
     public void run() 
     { 
      try 
      { 
       Thread.sleep (1000); 
      } 
      catch (InterruptedException e) 
      { 
       e.printStackTrace(); 
      } 
      System.out.println ("Starting repaint calls"); 
      for (int i = 0; i < 100000; i++) 
      { 
       component.repaint(); 
      } 
      System.out.println ("Finishing repaint calls"); 
     } 
    }).start(); 
} 

這是近似的輸出,你會看到(可能取決於計算機的速度,Java版本和許多其他條件):

1 
Starting repaint calls 
2 
3 
4 
5 
6 
Finishing repaint calls 
7 
8 

「1」 - 最初重繪當顯示框架時。
「2,3,4 ...」 - 由於來自單獨的非EDT線程的調用而發生其他七次重繪。

「但是我已經調用了100000重畫,而不是7!」 - 你會說。是的,重繪經理合並了那些在重新繪製隊列中相似和相同的時間。這是爲了優化重新繪製並加速整個UI。

順便說一句,您不需要從EDT調用repaint,因爲它不執行任何真正的繪畫,只是將組件更新排隊以備將來使用。這已經是線程安全的方法。

總結 - 在做某些其他操作(也可能導致其重新繪製)之前,確實需要重新繪製組件時,不應出現任何情況。當需要重新繪製組件時(在可能的情況下使用指定的矩形)調用重繪 - 重繪管理器將完成剩下的工作。這很好,除非你在paint方法裏面進行一些計算,這是完全錯誤的,可能會導致很多問題。

+0

secondaryLoop怎麼樣? http://sellmic.com/blog/2012/02/29/hidden-java-7-features-secondaryloop/ 我相信這可以讓我做到這一點(它的工作原理,我測試了它) – Karlovsky120

+0

@ Karlovsky120我只是想知道你需要用這麼精確的時間重新塗漆嗎?我能想到的唯一選擇是你在繪製代碼中執行一些可變操作,這是我以前說過的非常糟糕的事情。 –

+0

@ Karlovsky120順便說一句,感謝鏈接到主題描述SecondaryLoop - 它真的非常棒的UI開發工具! –