我正在为我的OOP入门论文中的一个项目在C#上做一个纸牌游戏,现在游戏已经开始工作了,但我正在为图形用户界面添加"flair“。
目前,牌是发牌的,并立即出现在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(),但它所实现的只是锁定我的图形用户界面,直到所有休眠处理完毕,然后一次显示所有卡片。
我也尝试过使用计时器和while循环的组合,但效果相同。
达到预期结果的最佳方式是什么?
发布于 2009-09-13 17:53:06
问题是,您在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-13 17:53:52
最便宜的解决方法是调用Application.DoEvents()进行循环,但更好的替代方法是设置一个System.Windows.Forms.Timer,您可以在它第一次运行后停止它。在这两种情况下,您都需要一些指示器来告诉您的UI事件处理程序忽略输入。如果足够简单,您甚至可以仅使用timer.Enabled属性来实现此目的。
发布于 2009-09-13 18:45:36
通常,我会简单地推荐这样的函数来执行暂停,同时允许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;
}
}
https://stackoverflow.com/questions/1419363
复制相似问题