2009-09-14 4 views
5

Я работаю над карточной игрой на C# для проекта на моей книге Intro to OOP, и теперь у меня есть игра, но я добавляю «чутье» в графический интерфейс.Приостановить выполнение метода без блокировки GUI. C#

В настоящее время карточки сдаются и отображаются в пользовательском интерфейсе мгновенно. Я хочу, чтобы на минутку запрограммировать паузу после того, как вы открыли карточку, прежде чем она начнет действовать дальше.

Когда игра запускается следующий код работает для заполнения 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); между каждой сделкой(). show(), а также внутри каждого из этих методов, но все, что она достигает, блокирует мой графический интерфейс до тех пор, пока все спящие не обработают, а затем сразу отобразите все карты.

Я также попытался использовать комбинацию таймера и цикла while, но это привело к такому же эффекту.

Что было бы лучшим способом достижения желаемого результата?

ответ

17

Проблема в том, что любой код, который вы запускаете в пользовательском интерфейсе, заблокирует пользовательский интерфейс и заморозит программу. Когда ваш код работает (даже если он работает Thread.Sleep), сообщения (такие как Paint или Click), отправленные в пользовательский интерфейс, не будут обрабатываться (пока элемент управления не вернется к контуру сообщения при выходе из обработчика события), что приведет к его замораживанию.

Лучший способ сделать это, чтобы работать в фоновом потоке, а затем Invoke в поток пользовательского интерфейса между снами, как это:

//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

Спасибо, делегат отлично работает! Кроме того, я никогда не хотел, чтобы на самом деле был задействован звонок по таймеру (я просто делал в нем сценарий типа count ++), который, вероятно, будет полезен и в будущем! – Windos

+0

Как я уже говорил, просто выполнение 'count ++' в таймере не поможет, потому что код в цикле while все равно будет блокировать поток пользовательского интерфейса. – SLaks

+0

Кстати, можно также использовать итераторы для этого; если вам интересно, я объясню. – SLaks

2

Я бы поставил код, который касается колоды (и вызывает Thread.Sleep) в другом потоке.

3

Дешевый выход был бы связан с вызовами Application.DoEvents(), но лучшей альтернативой было бы установить System.Windows.Forms.Timer, который вы остановите после первого раза, когда он истечет. В любом случае вам понадобится индикатор, чтобы сообщить обработчикам событий UI игнорировать ввод. Вы даже можете использовать свойство timer.Enabled для этой цели, если оно достаточно простое.

3

Обычно я просто рекомендую такую ​​функцию, чтобы выполнить паузу, позволяя интерактивному интерфейсу.

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; 
    } 
}