2009-09-14 49 views
5

我正在爲OOP論文介紹一個C#項目的紙牌遊戲,並且現在已經開始遊戲了,但我正在爲GUI添加「天賦」。暫停執行沒有鎖定GUI的方法。 C#

當前卡片被處理並立即出現在UI上。我想在處理一張卡之前暫停一會兒,然後再處理下一張卡。

當遊戲開始下面的代碼運行填充代表他們的PictureBoxes(將是最終的循環):

 cardImage1.Image = playDeck.deal().show(); 
     cardImage2.Image = playDeck.deal().show(); 
     cardImage3.Image = playDeck.deal().show(); 
     cardImage4.Image = playDeck.deal().show(); 
     cardImage5.Image = playDeck.deal().show(); 
     ... 

我有使用System.Threading.Thread.Sleep(100)嘗試;在每個deal()。show()之間,也在每個這些方法內,但它所實現的所有功能都是鎖定我的GUI,直到所有睡眠都處理完畢,然後立即顯示所有的卡片。

我也試過使用定時器和while循環的組合,但它導致了相同的效果。

達到預期效果的最佳方法是什麼?

回答

17

的問題是,您在UI運行任何代碼將阻止用戶界面和凍結程序。當你的代碼正在運行時(即使它正在運行Thread.Sleep),發送到UI的消息(例如Paint或Click)將不會被處理(直到退出事件處理程序時控制返回到消息循環),導致它凍結。

做到這一點,最好的辦法是在後臺線程運行,然後Invoke嗜睡之間的UI線程,就像這樣:

//From the UI thread, 
ThreadPool.QueueUserWorkItem(delegate { 
    //This code runs on a backround thread. 
    //It will not block the UI. 
    //However, you can't manipulate the UI from here. 
    //Instead, call Invoke. 
    Invoke(new Action(delegate { cardImage1.Image = playDeck.deal().show(); })); 
    Thread.Sleep(100); 

    Invoke(new Action(delegate { cardImage2.Image = playDeck.deal().show(); })); 
    Thread.Sleep(100); 

    Invoke(new Action(delegate { cardImage3.Image = playDeck.deal().show(); })); 
    Thread.Sleep(100); 

    //etc... 
}); 
//The UI thread will continue while the delegate runs in the background. 

或者,你可以做一個計時器,顯示每個圖像在下一個計時器中打勾。如果你使用一個定時器,你應該在開始時做的是啓動定時器;不要等待它,否則你會引入同樣的問題。

+0

謝謝,代表很棒!另外,我從來沒有把實際的交易調用放入計時器的時間週期中(我只是在做一個計數++類型的場景),這在將來可能會有用! – Windos

+0

正如我所解釋的,只是在計時器中執行'count ++'將無濟於事,因爲while循環中的代碼仍然會阻塞UI線程。 – SLaks

+0

順便說一句,它也可能誤用迭代器來做到這一點;如果你有興趣,我會解釋。 – SLaks

2

我會嘗試把代碼,在另一個線程處理甲板(並調用Thread.Sleep)。

3

便宜的出路是循環調用Application.DoEvents(),但更好的選擇是設置一個System.Windows.Forms.Timer,它會在第一次過後停止。無論哪種情況,您都需要一些指示器來告訴您的UI事件處理程序忽略輸入。如果足夠簡單,你甚至可以使用timer.Enabled屬性來達到此目的。

3

通常我會推薦一個像這樣的函數來執行暫停,同時允許UI進行交互。

private void InteractivePause(TimeSpan length) 
    { 
    DateTime start = DateTime.Now; 
    TimeSpan restTime = new TimeSpan(200000); // 20 milliseconds 
    while(true) 
    { 
     System.Windows.Forms.Application.DoEvents(); 
     TimeSpan remainingTime = start.Add(length).Subtract(DateTime.Now); 
     if (remainingTime > restTime) 
     { 
      System.Diagnostics.Debug.WriteLine(string.Format("1: {0}", remainingTime)); 
      // Wait an insignificant amount of time so that the 
      // CPU usage doesn't hit the roof while we wait. 
      System.Threading.Thread.Sleep(restTime); 
     } 
     else 
     { 
      System.Diagnostics.Debug.WriteLine(string.Format("2: {0}", remainingTime)); 
      if (remainingTime.Ticks > 0) 
       System.Threading.Thread.Sleep(remainingTime); 
      break; 
     } 
    } 
    } 

但似乎是在使用時從事件處理程序中調用此類解決方案的一些併發症,如按鈕點擊。我認爲系統希望按鈕單擊事件處理程序在它將繼續處理其他事件之前返回,因爲如果我在事件處理程序仍在運行時嘗試再次單擊,則該按鈕會再次按下,即使我試圖拖動窗體而不是點擊按鈕。

所以這裏是我的選擇。在表單上添加一個計時器,並創建一個經銷商類以處理與該計時器交互的卡片。設置定時器的Interval屬性以匹配您想要發送卡片的時間間隔。這是我的示例代碼。

public partial class Form1 : Form 
{ 

    CardDealer dealer; 

    public Form1() 
    { 
    InitializeComponent(); 
    dealer = new CardDealer(timer1); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
    dealer.QueueCard(img1, cardImage1); 
    dealer.QueueCard(img2, cardImage2); 
    dealer.QueueCard(img3, cardImage1); 
    } 
} 

class CardDealer 
{ 
    // A queue of pairs in which the first value represents 
    // the slot where the card will go, and the second is 
    // a reference to the image that will appear there. 
    Queue<KeyValuePair<Label, Image>> cardsToDeal; 
    System.Windows.Forms.Timer dealTimer; 

    public CardDealer(System.Windows.Forms.Timer dealTimer) 
    { 
    cardsToDeal = new Queue<KeyValuePair<Label, Image>>(); 
    dealTimer.Tick += new EventHandler(dealTimer_Tick); 
    this.dealTimer = dealTimer; 
    } 

    void dealTimer_Tick(object sender, EventArgs e) 
    { 
    KeyValuePair<Label, Image> cardInfo = cardInfo = cardsToDeal.Dequeue(); 
    cardInfo.Key.Image = cardInfo.Value; 
    if (cardsToDeal.Count <= 0) 
     dealTimer.Enabled = false; 
    } 

    public void QueueCard(Label slot, Image card) 
    { 
    cardsToDeal.Enqueue(new KeyValuePair<Label, Image>(slot, card)); 
    dealTimer.Enabled = true; 
    } 
}