2012-11-27 86 views
2

與此相似的問題已多次提出。見例如herehereSwing ContentPane在使用(重新)驗證,重新繪製後未更新

但我真的想明白爲什麼它是我的代碼不起作用。正如在這個問題的其他版本中已經回答的那樣,CardLayout可能就足夠了,儘管在我的情況下我不確定它是否理想。無論如何,我感興趣的是理解概念爲什麼這不起作用。

我有一個JFrame的內容窗格監聽關鍵事件。在內容窗格中按下某個鍵時,內容窗格會通知JFrame使用新的內容窗格更新自己。下面是該問題的一個簡單示例:

此代碼是完全可編譯的。您可以複製粘貼並按原樣運行。

這裏是我的JFrame:

import javax.swing.*; 
import java.awt.*; 
import java.util.Random; 

public class SimpleSim extends JFrame{ 

    private static SimpleSim instance = null; 

    public static SimpleSim getInstance(){ 
     if(instance == null){ 
      instance = new SimpleSim(); 
     } 

     return instance; 
    } 

    private SimpleSim(){} 

    public void initialize(){ 

     this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     this.setExtendedState(Frame.MAXIMIZED_BOTH); 
     this.pack(); 
     this.setVisible(true); 

     update(); 
    } 

    public void update(){ 
     System.out.println("SIMPLE_SIM UPDATE THREAD: " + Thread.currentThread().getName()); 
     Random rand = new Random(); 

     float r = rand.nextFloat(); 
     float g = rand.nextFloat(); 
     float b = rand.nextFloat(); 

     SimplePanel simplePanel = new SimplePanel(new Color(r, g, b)); 
     JPanel contentPane = (JPanel) this.getContentPane(); 

     contentPane.removeAll(); 
     contentPane.add(simplePanel); 
     contentPane.revalidate(); 
     contentPane.repaint(); 

     validate(); 
     repaint(); 

    } 


} 

而且這裏是我的JPanel充當我的內容窗格:奇怪的是

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.KeyEvent; 
import java.awt.event.KeyListener; 


public class SimplePanel extends JPanel implements KeyListener { 

    public SimplePanel(Color c){ 

     setFocusable(true); 
     setLayout(null); 
     setBackground(c); 
     setVisible(true); 
     this.addKeyListener(this); 
    } 
    public void keyTyped(KeyEvent keyEvent) { 
     if(keyEvent.getKeyChar() == 'a'){ 
      System.out.println("a"); 
      System.out.println("SIMPLE_PANEL KEY PRESS THREAD: " + Thread.currentThread().getName()); 
      SimpleSim.getInstance().update(); 
     } 
    } 

    public void keyPressed(KeyEvent keyEvent) { 
    } 
    public void keyReleased(KeyEvent keyEvent) { 
    } 
} 

,它工作在第一時間按a,而不是之後。我的猜測是這裏有一個線程問題。我可以看到,當第一次調用update時,它在主線程中被調用。下一次在美國東部時間召開。我已經嘗試使用invokeLater()調用update(),那也不起作用。我發現了一種使用不同設計模式的解決方法,但我真的很想理解爲什麼這樣做不起作用。

而且,簡單的類來運行:

public class Run { 

    public static void main(String[] args){ 
     SimpleSim.getInstance().initialize(); 
    } 
} 

注:看似冗餘呼叫驗證並重新繪製的JFrame做的目的是試圖安撫貼我提供的第二個鏈接,其中指出在意見即: 在最高受影響的組件上調用validate()。這可能是Java渲染週期中最泥濘的一點。對無效的調用將組件及其所有祖先標記爲需要佈局。驗證調用執行組件及其所有後代的佈局。一個工作「上」,另一個工作「下」。您需要調用樹中最高組件的驗證,這些組件將受到您的更改的影響。 我以爲這會導致它的工作,但無濟於事。

+0

儘管我沒有相反的證據(除了'main'方法),但所有與UI的交互都必須在EDT的上下文中執行。你可能會這樣做,但你做的幾個陳述關注我。如果你開始的時候很好,小心,那麼請隨時忽略這個聲明...... – MadProgrammer

回答

4

我做了一些修改你的代碼,很抱歉,但它使測試SOOOO容易得多......

進口的變化,我可以看到的是在更新方法。基本上我簡單地在框架上稱爲revalidate

重新驗證規定

重新驗證組件層次到最接近的驗證根。

該方法首先使組件層次結構失效,該組件層次結構從 該組件直到最近的驗證根。之後, 組件層次結構將從最近的驗證根 開始驗證。

這是一種方便的方法,可以幫助應用程序開發人員避免手動查找驗證根。基本上,它相當於 首先調用此組件上的invalidate()方法,然後 調用最近驗證根上的validate()方法。

我認爲最後一部分是你在自己的代碼中缺少的。

public class SimpleSim extends JFrame { 

    private static SimpleSim instance = null; 

    public static SimpleSim getInstance() { 
     if (instance == null) { 
      instance = new SimpleSim(); 
     } 

     return instance; 
    } 

    private SimpleSim() { 
    } 

    public void initialize() { 

     this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     this.setSize(400, 400); 
     this.setVisible(true); 
     setLayout(new BorderLayout()); 

     update(); 

    } 

    public void update() { 
     System.out.println("NEXT: " + Thread.currentThread().getName()); 
     Random rand = new Random(); 

     float r = rand.nextFloat(); 
     float g = rand.nextFloat(); 
     float b = rand.nextFloat(); 

     SimplePanel simplePanel = new SimplePanel(new Color(r, g, b)); 
     JPanel contentPane = (JPanel) this.getContentPane(); 

     getContentPane().removeAll(); 
     add(simplePanel); 

     revalidate(); 
    } 

    public class SimplePanel extends JPanel { 

     public SimplePanel(Color c) { 

      setFocusable(true); 
      setLayout(null); 
      setBackground(c); 
      setVisible(true); 
      requestFocusInWindow(); 
      InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW); 
      ActionMap am = getActionMap(); 

      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), "A"); 
      am.put("A", new AbstractAction() { 
       @Override 
       public void actionPerformed(ActionEvent e) { 
        System.out.println("a"); 
        System.out.println("KEY: " + Thread.currentThread().getName()); 
        SimpleSim.getInstance().update(); 
       } 
      }); 

     } 
    } 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
       } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { 
       } 

       SimpleSim.getInstance().initialize(); 
      } 
     }); 
    } 
} 

另外,我建議你把而是採用KeyListener的優勢key bindings API的。這將解決一些重點問題;)

UPDATE

一些時間測試各種排列後,我們來,主要的問題是關係到一個焦點問題的結論。

雖然SimplePanel是可以關注的,但沒有任何東西可以讓它關注,這意味着關鍵聽衆無法被觸發。

已添加simplePanel.requestFocusInWindow添加到框架後,似乎允許按鍵偵聽器保持活動狀態。

從我自己的測試中,通過撥打電話revalidate面板沒有更新。

+0

+1。但是,我不認爲這個問題是用revalidate()。你也可以添加我的代碼(實際上,我也試過了),但它沒有修復它。我把過多的(重新)驗證,重繪,打包等電話稱爲說明我不認爲這個問題是用swing來驗證組件層次。這裏的問題是一個線程問題,你的代碼修復它的原因是你最初使用EventQueue的invokeLater調用來運行程序。這是我想要澄清與我的問題。這爲什麼解決它?我的原始代碼中的線程有什麼問題? – smaccoun

+0

此外,關鍵綁定API在其他問題上被提及,這是非常合理的建議。我將在未來使用 – smaccoun

+0

Swing不是線程安全的。您可能因競賽條件或其他線程異常而被訪問。當測試你的代碼時,'revalidate'是唯一真正起作用的 – MadProgrammer