WPF, how to implement async/await? - c#

I'm learning how to webscrape in WPF. I check the site every 20sec, update my ObservableCollection (myClients) according to search results and display it in Listview (myList). I have 2 Buttons, one to start search and one to stop it.
I didn't know how to implement button autoclick every X sec (which would solve all my problems, am i right?) so i had to use Task.Delay(20000). Program works, it doesn't freeze right at the start like if i had used Thread.Sleep(), but if i press the Stop button and then Start, everything freezes.
I will upload only portion of the code that seems to be the problem. Note that the whole program at the moment is mostly reverse-engineered from several different programs as i am still a beginner.
private async void Button_Click(object sender, RoutedEventArgs e) //Start button
{
string car;
string price;
string link;
wantToAbort = false;
while (!wantToAbort)
{
// ----Simulate GET request----
//-----End GET----
myList.ItemsSource = myClients;
string searchCar = txtBlock.Text + " " + txtBlock2.Text;
var articleNodes = htmlDoc.DocumentNode.SelectNodes($"//*[#id='main_content']/div[1]/div[2]/ul[1]//*[text()[contains(., '{searchCar}')]]");
if (articleNodes != null && articleNodes.Any())
{
foreach (var articleNode in articleNodes)
{
car = WebUtility.HtmlDecode(articleNode.InnerText);
price = WebUtility.HtmlDecode(articleNode.ParentNode.ParentNode.SelectSingleNode("span").InnerText);
link = WebUtility.HtmlDecode(articleNode.ParentNode.ParentNode.Attributes["href"].Value);
var tempUser = new User(car, price, link);
if (!myClients.Any(x=>x.Link == tempUser.Link))
{
myClients.Insert(0, tempUser); //Inserts new item if Links are different
txtBlock3.Text = "Searching...";
}
}
await Task.Delay(20000); //This seems to be an issue
}
}
}
private void Button_Click_1(object sender, RoutedEventArgs e) //Stop button
{
wantToAbort = true;
txtBlock3.Text = "Ready to search again!";
}

Running a while loop on the UI thread may freeze the application as the UI thread cannot both process UI events and execute a loop or doing anything else simultaneously.
If you want to do something every x seconds you could use a timer as suggested by EJoshuaS. There is a DispatcherTimer class in WPF that fires a Tick event on the UI thread at an interval specified by the Interval property: https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer%28v=vs.110%29.aspx
You don't want to perform the GET request to the web server on the UI thread though so you should probably use a System.Timer.Timer: https://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx. This is a different type of timer that runs on a background thread.
Since you can only access UI controls such as TextBlocks and ListBoxes on the thread on which they were originally created - that is the UI thread - you will have to use the dispatcher to marshall any code that access these controls back to the UI thread in your Elapsed event handler:
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
//call the web server here....
//dispatch any access to any UI control
txtBlock3.Dispatcher.Invoke(new Action(() = > { txtBlock3.Text = "Searching..."; }));
}
The golden rule to maintain a responsive application is to execute any long-running code on a background thread but you must only access UI controls back on the UI thread. Please refer to MSDN for more information about the threading model in WPF: https://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx

DispatcherTimer may be a better solution in this case, like in the below example:
public partial class MainWindow : Window
{
private DispatcherTimer timer;
public MainWindow()
{
InitializeComponent();
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 220);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
// Do something on your UI
Trace.TraceInformation("Timer expired");
}
}
Basically, this will raise an event at a given interval. Note that Windows Forms also has a timer, as does System.Threading, but you want to make sure you use DispatcherTimer rather than those. In particular, the one from System.Threading tends not to mix well with UIs because it runs its actions on the thread pool and WPF in particular is very fussy about how you update your UI from background threads.
The documentation I link to, as well as this answer, also give details on this.

Related

C# Invoke button control on separate thread

I have seen a lot of questions about how to edit controls on c# form from a different thread but none make much sense to me. I understand that you can not change any UI from another thread than it's main. To make this work you have to use invoke and from there safely edit the control?
I have a button that starts writing in a file and the moment you press the button the button itself gets disabled so you can not start multiple threads that do exactly the same. When the writing is done I want the button to be available again but I can not get it working on this other thread.
I have this as the Generate_Click event from the form.
private void Generate_Click(object sender, EventArgs e)
{
Generate.Enabled = false;
int x = 512;
int y = 512;
MBrot mbrot = new MBrot(x, y);
PB_Update lb = new PB_Update(0, y, Generator_PB, Generate, mbrot, this);
lb.Start();
}
And this is in PB_Update.cs the ThreadWork() function, when the while loop is done the writing to the file is done and so is the thread so its ended and given a messagebox with "finished" now as last the button needs to be enabled again.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.Enabled = true;
}
For WinForms you can execute directly on the thread which the control was created on through the Control.BeginInvoke method, you can use Control.Invoke as well but, Control.BeginInvoke is preferred for UI operations.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.BeginInvoke((Action)delegate()
{
Generate_.Enabled = true;
});
}
Somehow, get a reference to the form that hosts the generate_ button (let's call it myform). Then, at the bottom of your ThreadWork:
myform.Invoke(new Action(() => {
myform.SetGenerateEnabled();
}));
And then inside your form create that method that enables the button appropriately. (I used a method rather than just updating the button directly so that you don't publicly expose the button.)
This executes the commands inside the { ... } on myform's thread, which is a UI thread, because it is UI. At least, that's what I understand. This is how I do all of my UI updating from other threads.
Here's a simple example of a way to kick off an async task that disables a button for 5 seconds and then enables it again. Meanwhile, the rest of the UI is functional.
Note that this async method exists in the same class as your Generate_Click event, and runs on the UI thread. This means that it can enable and disable the button. But the long running task executes on a separate thread, so it doesn't lock the UI.
Hopefully this sample provides you a base to modify for your own code:
private void Generate_Click(object sender, EventArgs e)
{
DisableButton(sender as Button, 5);
}
private async void DisableButton(Button sender, int secondsToDisable)
{
sender.Enabled = false;
// In your code, you would kick off your long-running process here as a task
await Task.Run(()=>Thread.Sleep(TimeSpan.FromSeconds(secondsToDisable)));
sender.Enabled = true;
}

How to implement a popup window with a counter

I would like to implement a simple popup window in Windows Forms, which will show a simple timer to the user while some slow-running process is executing. The premise is simple; show to the user that something is indeed going on and the application is not frozen. Note that this slow-running process is not a loop, nor is it something that I can tap into.
What I want is a simple popup window, showing some message along the lines "Elapsed time: x seconds", where x is incremented every second.
The basic concept is the following:
public void test()
{
//Some code which does stuff
//Popup window with counter
//Perform long running process
//Close popup window with counter
//Some other code which does other stuff
}
I tried to do it using various ways, including background workers, threads, and of course timers. But I did not manage to make it work as I wanted. And I would prefer not to post any of my code so as not to "lead" the responses to a specific way of doing this.
So what would be the best way to do this work?
Thanks.
UPDATE:
In reply to some comments, since I cannot paste any code in the replies section, I'm editing my original question to accomodate this. One of the implementations that I tried is to spawn the popup window in a separate thread. Although I got no runtime errors, the popup window did not refresh correctly. It indeed poped-up, but no text would show within it, and the counter would not refresh. Here's the code:
private void test()
{
frmProgressTimer ofrmProgressTimer = new frmProgressTimer(); //Instance of popup Form
System.Threading.Tasks.Task loadTask = new System.Threading.Tasks.Task(() => ProgressTimer(ofrmProgressTimer));
loadTask.Start();
//Perform long running process
System.Threading.Tasks.Task cwt = loadTask.ContinueWith(task => EndProgressTimer(ofrmProgressTimer));
}
private void ProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Show();
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.startTimer();
}));
}
private void EndProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
}
And here's my popup form code:
public partial class frmProgressTimer : Form
{
private int counter = 0;
private Timer timer1;
public frmProgressTimer()
{
InitializeComponent();
timer1 = new Timer();
timer1.Interval = 1000;
timer1.Tick += new EventHandler(timer1_Tick);
}
public void startTimer()
{
timer1.Start();
}
public void stopTimer()
{
timer1.Stop();
}
private void timer1_Tick(object sender, EventArgs e)
{
counter += 1;
labelText.Text = counter.ToString();
}
}
This is actually quite easy to do. Create your dialog, define your long running operation to take place in a non-UI thread when it is shown, add a continuation to that operation which closes the dialog when the task finishes, and then show the dialog.
MyDialog dialog = new MyDialog();
dialog.Shown += async (sender, args) =>
{
await Task.Run(() => DoLongRunningWork());
dialog.Close();
};
dialog.ShowDialog();
The code to have the ticking over time should be entirely contained within the dialog, and based on the question it seems you already have that well under control with a simple Timer.
Make a new form, which will pop up, and show a timer. That way it won't be interrupted with all the work on your main form, and the timer will work continuously.
Remember when showing a new from to use newForm.ShowDialog() not newForm.Show(). Your can google the differences
I would simply start your work on a separate thread. Launch a modal form with your timer output. To display the timer use an actual timer instance set to update every second. When the timer event fire update your dialog.
Finally once you're thread completes close the dialog so your main form is active again.
First of all you need to make it not closeable by the user (as if modal dialogs weren't annoying enough) but closeable by your code. You could accomplish this by subscribing to the FormClosing event of the form. Let's say your popup form's name is Form2:
private bool mayClose = false;
public void PerformClose()
{
this.mayClose = true;
this.Close();
}
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (!this.mayClose)
e.Cancel = true;
}
Create a Timer, provide a Tick event handler, enable it and set its Interval to 500 milliseconds:
Create a label to host your desired text. Let's call it label1.
Within and surrounding your Tick event handler do something like this:
private DateTime appearedAt = DateTime.UtcNow;
private void timer1_Tick(object sender, EventArgs e)
{
int seconds = (int)(DateTime.UtcNow - this.appearedAt).TotalSeconds;
this.label1.Text = string.Format(#"Ellapsed seconds: {0}", seconds);
}
Make sure your long running process is happening on a background thread, not on the GUI thread.
Say your long running process can be thought of as the execution of a method called MyProcess.
If that is the case, then you need to call that method from a secondary thread.
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
Show the form right before the long running process starts. This can be done in any of the 2 places (marked in the previous section of code) called PLACE1 or PLACE2. If you do it in PLACE2 you will have to marshal a call back to the GUI thread in order to be able to interact with the WinForms framework safely. Why am I bringing this up ? It's because maybe the long running process is not started from within the GUI thread at all and you absolutely need to do this.
Close the form right after the long running process finishes. This can be done only in PLACE3 and you absolutely need to marshal a call.
To wrap the earlier 2 bullets and the answer, you could do this:
private void DoIt()
{
Form2 form2 = new Form2();
Action showIt = () => form2.Show();
Action closeIt = () => form2.PerformClose();
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
form2.BeginInvoke(showIt);
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
form2.BeginInvoke(closeIt);
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
}
Finally I've managed to resolve this in the most simplistic manner. And it works like a charm. Here's how to do it:
//Create an instance of the popup window
frmProgressTimer ofrmProgressTimer = new frmProgressTimer();
Thread thread = new Thread(() =>
{
ofrmProgressTimer.startTimer();
ofrmProgressTimer.ShowDialog();
});
thread.Start();
//Perform long running process
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
You can see the code for the popup window in the original post/question, with the only difference that the tick function changes the label text as:
labelText.Text = string.Format("Elapsed Time: {0} seconds.", counter.ToString());
Thank you to everybody for trying to help me out.

C# Asynchronous Task for a Stock Ticker

I've been trying to learn more about asynchronous tasks and threading but not making a ton of headway.
I'm trying to load an "Engine" type of thread that will run in the background upon launch and be able to access the UI Thread to update variables, without hanging the UI Thread.
In the below code, Engine is called, and a Ticker object is created which holds the current value of (Litecoin/USD) called Last, also holds several other values that would be useful. This code successfully assigns the current value to label1.text. I don't necessarily need code but what approach would I take to create a ticker object in the background every second and update the UI thread with each new Ticker objects values.
Is this a good case for a background worker?
private void Form1_Load(object sender, EventArgs e)
{
Engine();
}
private void Engine()
{
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD:" + ltcusd.Last;
}
EDIT:
If I do the following, label1 throws an InvalidOperationException due to a Cross-thread operation attempt (label1 in the UI thread).
private void Form1_Load(object sender, EventArgs e)
{
var t = Task.Factory.StartNew(() => Engine());
t.Start();
}
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
}
Using async/await, the simplest way of getting an "asynchronous" sort of API is to invoke a new task. It's not great, but it'll make things simpler. I would probably create a new class which basically wrapped all the BtceApi methods in tasks:
public class BtceApiAsync
{
public Task<Ticker> GetTickerAsync(BtcePair pair)
{
return Task.Run(() => BtceApi.GetTicker(pair));
}
// etc
}
Then you can use a timer which fires once per second, which will start off a new task and update the UI appropriately:
// Keep a field of type System.Windows.Forms.Timer
timer = new Timer();
timer.Interval = 1000;
timer.Tick += DisplayTicker;
timer.Start();
...
private async void DisplayTicker(object sender, EventArgs e)
{
Ticker ticker = await BtceApiAsync.GetTickerAsync(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
Note that this doesn't mean the screen will be updated once per second... there will be a new task started once per second, and as soon as each task completes, the UI will be updated.
The use of await here - from an async method started on the UI thread - means you don't need to worry about using the UI; the whole async method will execute on the UI thread, even though the fetch itself happens in a different thread.
You can try ContinueWith to update the Label at the end of the task. If you want to update it event before the task ends then raise an event which is registered by on the UI thread. The event can then update the label.
I suppose this is Windows Forms. You could do it "old school style" and set the label text on the UI thread, and you can do that by passing delegate to the BeginInvoke or Invoke method.
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
UpdateText("LTC/USD: " + ltcusd.Last);
}
}
private void UpdateText(string text)
{
//Inspect if the method is executing on background thread
if (InvokeRequired)
{
//we are on background thread, use BeginInvoke to pass delegate to the UI thread
BeginInvoke(new Action(()=>UpdateText(text)));
}
else
{
//we are on UI thread, it's ok to change UI
label1.Text = text;
}
}

Change DispatchTimers to other timer to solve performance issues. Cannot update UI, program shutting down

EDIT #1: I have placed worker.RunWorkerAsync() within my timer loop and my application does not shut down anymore. Although nothing seems to happen now.
For performance reasons i need to replace DispatcherTimers with a other timer that runs in a different thread. There are to much delays / freezes so DispatcherTimer is no longer a option.
I am having problems to actually update my GUI thread, my application always seems to shut down without any warnings / errors.
I have mainly been trying to experiment with BackGroundWorker in attempt to solve my problem. Everything results in a shut down of my application when i launch it.
Some code examples would be greatly apperciated.
Old code dispatcher code:
public void InitializeDispatcherTimerWeging()
{
timerWegingen = new DispatcherTimer();
timerWegingen.Tick += new EventHandler(timerWegingen_Tick);
timerWegingen.Interval = new TimeSpan(0, 0, Convert.ToInt16(minKorteStilstand));
timerWegingen.Start();
}
private void timerWegingen_Tick(object sender, EventArgs e)
{
DisplayWegingInfo();
CaculateTimeBetweenWegingen();
}
Every 5 seconds the DisplayWegingInfo() and Calculate method should be called upon.
The GUI updates happen in the Calculate method. There a button gets created dynamically and added to a observerableCollection.
Button creation (short version):
public void CreateRegistrationButton()
{
InitializeDispatcherTimerStilstand();
RegistrationButton btn = new RegistrationButton(GlobalObservableCol.regBtns.Count.ToString());
btn.RegistrationCount = GlobalObservableCol.regBtnCount;
btn.Title = "btnRegistration" + GlobalObservableCol.regBtnCount;
btn.BeginStilstand = btn.Time;
GlobalObservableCol.regBtns.Add(btn);
GlobalObservableCol.regBtnCount++;
btn.DuurStilstand = String.Format("{0:D2}:{1:D2}:{2:D2}", 0, 0, 0);
}
New code using threading timer that runs in a different thread then the GUI
public void InitializeDispatcherTimerWeging()
{
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(Worker_DoWork);
worker.RunWorkerAsync();
}
void Worker_DoWork(object sender, DoWorkEventArgs e)
{
TimerCallback callback = MyTimerCallBack;
timerWegingen = new Timer(callback);
timerWegingen.Change(0, 5000);
}
private void MyTimerCallBack(object state)
{
DisplayWegingInfo();
CaculateTimeBetweenWegingen();
}
I timer runs in a separate thread then the GUI thread (that dispatcherTimer uses). But i cannot seem to be able to send this update to the UI thread itself so the updates get actually implemented in the UI.
The button gets refilled with new values every 1 sec trough a other timer. "DuurStilstand" is a dependency property
private void FillDuurStilstandRegistrationBtn()
{
TimeSpan tsSec = TimeSpan.FromSeconds(stopWatch.Elapsed.Seconds);
TimeSpan tsMin = TimeSpan.FromMinutes(stopWatch.Elapsed.Minutes);
TimeSpan tsHour = TimeSpan.FromMinutes(stopWatch.Elapsed.Hours);
if (GlobalObservableCol.regBtns.Count >= 1
&& GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].StopWatchActive == true)
{
GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].DuurStilstand =
String.Format("{0:D2}:{1:D2}:{2:D2}", tsHour.Hours, tsMin.Minutes, tsSec.Seconds);
}
}
Would i need to use the invoke from Dispatcher in the above method? If so how exactly?
Not sure how to call the ui thread after initializing the doWork method of the BackGroundWorker, my application keeps shutting down after right after start up.
I have tried using Dispatcher.BeginInvoke in several methods but all failed so far. At the moment i have no clue how to implement it.
All the above code is written in a separate c# class.
Best Regards,
Jackz
When I ran my sample of your code, the DisplayWegingInfo() was throwing an exception trying to access UI components. We need to call Invoke() from the Timer thread to update the UI. See DisplayWegingInfo() below. Note: this assumes that CaculateTimeBetweenWegingen() does not interact with the UI.
void Worker_DoWork(object sender, DoWorkEventArgs e)
{
TimerCallback callback = MyTimerCallBack;
timerWegingen = new System.Threading.Timer(callback);
timerWegingen.Change(0, 3000);
}
private void MyTimerCallBack(object state)
{
DisplayWegingInfo();
CaculateTimeBetweenWegingen();
}
private void DisplayWegingInfo()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(DisplayWegingInfo));
return;
}
// at this point, we are on the UI thread, and can update the GUI elements
this.label1.Text = DateTime.Now.ToString();
}
private void CaculateTimeBetweenWegingen()
{
Thread.Sleep(1000);
}

Pause execution of a method without locking GUI. C#

I'm working on a card game in C# for a project on my Intro to OOP paper and have got the game working now but am adding "flair" to the GUI.
Currently cards are dealt and appear on the UI instantaneously. I want to have to program pause for a moment after dealing a card before it deals the next.
When a game is started the following code runs to populate the PictureBoxes that represent them (will be a loop eventually):
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();
...
I have tries using System.Threading.Thread.Sleep(100); between each deal().show() and also inside each of those methods but all it achieves is locking up my GUI until all of the sleeps have processed then display all of the cards at once.
I have also tried using a combination of a timer and while loop but it resulted in the same effect.
What would be the best way of achieving the desired result?
The problem is that any code that you run on the UI will block the UI and freeze the program. When your code is running (even if it's running Thread.Sleep), messages (such as Paint or Click) sent to the UI will not be processed (until control returns to the message loop when you exit your event handler), causing it to freeze.
The best way to do this is to run on a background thread, and then Invoke to the UI thread between sleeps, like this:
//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.
Alternatively, you could make a timer and show each image in the next timer tick. If you use a timer, all you should do at the beginning is start the timer; don't wait for it or you'll introduce the same problem.
Normally I'd simply recommend a function like this to perform a pause while allowing the UI to be interactive.
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;
}
}
}
But there seems to be some complication in using such a solution when it is called from within an event handler such as a button click. I think the system wants the button click event handler to return before it will continue processing other events because if I try to click again while the event handler is still running, the button depresses again even though I'm trying to drag the form and not click on the button.
So here's my alternative. Add a timer to the form and create a dealer class to handle dealing with cards by interacting with that timer. Set the Interval property of the timer to match the interval at which you want cards to be dealt. Here's my sample code.
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;
}
}
The cheap way out would be to loop with calls to Application.DoEvents() but a better alternative would be to set a System.Windows.Forms.Timer which you would stop after the first time it elapses. In either case you'll need some indicator to tell your UI event handlers to ignore input. You could even just use the timer.Enabled property for this purpose if it's simple enough.
I would try puting the code that deals the deck ( and calls Thread.Sleep) in another thread.

Categories

Resources