GUI Programming used to be one of my nightmare in programming. I had a hard time understanding the concept and implemented it in my Windows Application. The good news is, I now know a little bit about it (^_^)/. Now by writing it into a blog post, I actually helping myself to understand the concept better, and hopefully, it might help others in one way or another. 🙂
In this project, we will create a stopwatch (might be useful to track-down how long you need to create a blog post (^_^)/ ). OK, now please open your Visual Studio, and create a Windows Application. Name the project ‘ThreadingGUI_02’. After that, design the form to look something like this:
So the idea is to create a process that will have a counter, increase the counter value and update the GUI every 0.1 seconds. In order to keep the GUI responsive to user’s actions, the actual processing should be done in different Thread. We also need to create a delegate, this way the the worker thread will be able to access the GUI thread and updates the corresponding labels.
Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; namespace ThreadingGUI_02 { public delegate void RefreshGUI(int i); public partial class Form1 : Form { Worker w; RefreshGUI myUpdateCounter; Thread t; public void UpdateCounter(int c) { if (this.InvokeRequired) { this.Invoke(myUpdateCounter, c); } else { int ms = c % 10; int second = (c / 10) % 60; int minute = (c / 600) % 60; int hour = (c / 36000) % 24; this.lblHour.Text = hour.ToString("00"); this.lblMinute.Text = minute.ToString("00"); this.lblSecond.Text = second.ToString("00"); this.lblMiliSecond.Text = ms.ToString("0"); } } public Form1() { InitializeComponent(); myUpdateCounter = new RefreshGUI(UpdateCounter); w = new Worker(myUpdateCounter); t = new Thread(new ThreadStart(w.Counting)); t.Start(); } private void button1_Click(object sender, EventArgs e) { Button b = (Button)sender; b.Text = b.Text == "Start" ? "Stop" : "Start"; w.isCounting = b.Text == "Stop"; this.button2.Enabled = b.Text == "Start"; } private void button2_Click(object sender, EventArgs e) { w.count = 0; int c = 0; int ms = c % 10; int second = (c / 10) % 60; int minute = (c / 600) % 60; int hour = (c / 36000) % 24; this.lblHour.Text = hour.ToString("00"); this.lblMinute.Text = minute.ToString("00"); this.lblSecond.Text = second.ToString("00"); this.lblMiliSecond.Text = ms.ToString("0"); } private void Form1_FormClosed( object sender, FormClosedEventArgs e) { t.Abort(); t = null; } private void label5_Click( object sender, EventArgs e) { } } }
Add a new class, and name it Worker.cs:
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ThreadingGUI_02 { class Worker { RefreshGUI update; public bool isCounting; public int count = 0; public Worker(RefreshGUI d) { update = d; isCounting = false; } public void Counting() { while (true) { if (isCounting) { ++count; if (update != null) update(count); } Thread.Sleep(100); } } } }
So as you see in the codes above, the logic of program would be:
- the main thread (a.k.a the GUI thread) instantiates the delegate
- GUI thread instantiates the worker thread, and pass the delegate object to worker thread object
- GUI thread starts the worker thread object
- the worker thread updates the GUI thread object using delegate
- the worker thread sleeps for a 0.1 seconds (If you do not need the 0.1 seconds delay, you could comment the Thread.Sleep(100); line)
This is example is not really a good example in threading since we never protect the ‘Critical Section‘ of the program with Mutex/Semaphore. Maybe in the future we will discuss how to prevent deadlock using Mutex/Semaphore.
For those who are lazy to copy and paste (^_^) into their Visual Studio, you can download the project solution: Threading GUI – Stopwatch