2012-11-20 74 views
1

所以我正在製作圖形卡遊戲。每張卡片都是一個JPanel,帶有一個按鈕和兩個與其關聯的圖像。我有一個翻轉方法,這是我點擊卡片時在動作偵聽器中首先呼叫的方法。未按順序發生Java圖形事件我期望他們

public void flip() 
{ 
    if(b1.getIcon() == card2) b1.setIcon(card1); 
    else b1.setIcon(card2); 
    revalidate(); 
    repaint(); 
} 

然而,由於某些原因,卡犯規翻轉(意味着圖標不改變),直到某一點後,我調用此方法。例如,當我在調用flip之後放置一個Thread.sleep時,我會認爲我的程序在翻轉完成後會暫停,但它不會!它睡眠的圖像尚未切換,並且只在睡眠時間結束後的某個點進行切換。

當我在這個紙牌遊戲中有一個人類玩AI時,由於AI事件發生在屏幕上翻牌前發生,即使我在執行任何AI代碼之前調用flip(),這會導致一些主要問題。任何人都可以告訴我這裏發生了什麼?

+0

你確定你可以在這裏用'=='運算符來比較'b1.getIcon()'和'card2'嗎?我知道我在 – yiwei

+0

之前遇到了問題。是的,翻轉總是最終發生,我認爲這意味着==確實起作用。我的問題是,屏幕上的圖像直到我期待它們纔會切換。 –

+1

一般注意事項:除非一個變量是一個基元,否則_always_使用'.equals()'進行比較,即使您沒有重寫它 - 如果必要的話,它會使實現更改更簡單。我建議在任何可能的時候總是使用花括號('{}'),而不僅僅是在需要時;它可以使理解控制流程變得更容易一些(特別是如果您沒有正確縮進代碼)。另外,如果可能的話,不要僅僅命名變量'card1','card2' - 使用諸如'face'和'back'之類的東西,或者其他東西(你是否必須分開卡片或者什麼?)。 –

回答

4

重繪是一個要求Swing重繪而不是調用重繪。在封面下,repaint()將重繪作業發佈到事件隊列中。隨着重繪工作,像鼠標移動,鼠標點擊,鍵盤活動等也張貼在那裏。 Swing線程周而復始地從該隊列中執行作業來重新繪製UI,向組件傳遞鼠標和鍵盤事件等。實際上,swing線程經常提供這些事件,但這取決於您的UI在做什麼工作在那個Swing線程上。當它不提供鼠標點擊,鍵盤事件和重繪時,它無法從該隊列中讀取。因此,如果您的代碼需要很長時間才能應對任何事件,那麼揮杆對新事件做出及時響應的能力就會降低,或者一起阻止。

這就是爲什麼如果您使用Swing事件調度線程通過網絡執行阻塞服務調用,那麼在該調用返回之前您的UI將停止繪圖。這就是爲什麼它重要的是移動長時間運行的作業,如網絡調用關閉Swing線程,並在調用回來時使用SwingUtilities.invokeLater(),以便您可以更新Event線程上的UI。 (invokeLater()通過將作業發佈到上面討論的Swing事件隊列中來完成此操作)。

這也是爲什麼在Swing事件線程上調用睡眠是一個可怕的想法。如果您讓該線程進入睡眠狀態,則無法爲隊列中的事件提供服務。並且在Swing線程正在休眠時,您要求的repaint()不會完成。

首先刪除你的睡眠電話。第二。在執行更多邏輯之前,不要編寫依賴繪畫的代碼來完成。重繪和重新驗證是特殊的調用,Swing將結合請求重新繪製/重新驗證,因此它不會在連續重繪10次(浪費重要的CPU時間)。

Swing無法重繪您的面板,除非您讓調用線程的線程保留它首次調用的方法。線程越早離開該方法越早重繪()將發生。

你沒有解釋爲什麼你要重繪立即發生,所以我不能給你任何關於如何構建你的代碼的建議,所以你不關心它。但我告訴你,你不應該在意重繪還沒有發生。

+2

另外看看:http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html – atamanroman

+0

+1,很好的答案,我記得當我有這個問題。 – Jimmt

1

完全同意@chubbsondubs。我只想補充一點,如果您的目標是在翻牌後一段時間發生某些事件,請考慮使用swing timers。正如你痛苦地意識到的那樣,調用Sleep並沒有達到預期的效果。但如果相反,你

  • 創建例如1秒
  • 創建一個動作一個計時器,當計時器觸發
  • 啓動定時器

,那麼你可以創建延遲要執行的你想不阻塞事件分派線程。

public void flip() 
{ 
    if(b1.getIcon() == card2) b1.setIcon(card1); 
    else b1.setIcon(card2); 
    revalidate(); 
    repaint(); 

    javax.swing.Timer t = new javax.swing.Timer(1000, new ActionListener() { 
     public void actionPerformed(ActionEvent e) { 
      // do something interesting 
     } 
    }); 
    t.repeats(false); 
    t.start(); 
} 
+0

小心點。定時器線程不是擺動線程!如果您開始觸摸模型並修改UI元素,那麼您違反了Swing的單線程規則。您必須從Swing線程執行這些操作。您可以使用此Timer將作業發佈到使用SwingUtilities.invokeLater()的Swing線程。 – chubbsondubs

+0

感謝您的評論。但我認爲我確定。我沒有指定java.util.Timer(它有你注意到的問題)。我指定了在EDT中執行的javax.swing.Timer。 –

相關問題