5

Intro to OOPペーパーのプロジェクトでC#のカードゲームに取り組んでおり、ゲームは現在機能していますが、GUIに「フレア」を追加しています。

現在、カードは配られ、UIに即座に表示されます。カードを配った後、次のカードを配る前に、一時停止をプログラムする必要があります。

ゲームが開始されると、次のコードが実行され、それらを表すPictureBoxにデータが入力されます(最終的にはループになります)。

        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ループを組み合わせて使ってみましたが、同じ効果が得られました。

望ましい結果を達成するための最良の方法は何でしょうか?

4

4 に答える 4

17

問題は、UIで実行するコードがUIをブロックし、プログラムをフリーズさせることです。コードが実行されている場合(実行中であってもThread.Sleep)、UIに送信されたメッセージ(ペイントやクリックなど)は処理されず(イベントハンドラーを終了するときに制御がメッセージループに戻るまで)、コードがフリーズします。

これを行うための最良の方法は、次のように、バックグラウンドスレッドで実行して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.

または、タイマーを作成して、次のタイマーティックで各画像を表示することもできます。タイマーを使用する場合、最初に行う必要があるのはタイマーを開始することだけです。それを待たないでください。さもないと同じ問題が発生します。

于 2009-09-14T01:53:06.183 に答える
3

安価な方法は、Application.DoEvents()の呼び出しでループすることですが、より良い代替手段は、最初に経過した後に停止するSystem.Windows.Forms.Timerを設定することです。いずれの場合も、UIイベントハンドラーに入力を無視するように指示するためのインジケーターが必要になります。十分に単純な場合は、この目的でtimer.Enabledプロパティを使用することもできます。

于 2009-09-14T01:53:52.470 に答える
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;
  }
}
于 2009-09-14T02:45:35.867 に答える
2

デッキを処理する(そしてThread.Sleepを呼び出す)コードを別のスレッドに入れてみます。

于 2009-09-14T01:53:48.850 に答える