Introduction
The following article describes a method to create a WPF project that provides a progress bar on a thread separate to your worker thread. The worker thread will be completing a task that will take 5 seconds, therefore you do not want your app to become unresponsive.
The code makes use of WPF windows, C# events, C# delegates. It would be useful if you are familiar with these types but it is not essential as I will explain what is needed.
In order to keep this tutorial as simple as possible, I will not make use of any WPF functionality to improve the appearance of the windows.
Application Design
The project contains two WPF windows and one class:
- The first window named
MainWindow
will display information for the user. The window contains one button titled 'Begin Processing' and one label that will display a message to the user from your worker method.
- The second window will host your progress bar. This window will be displayed while your 5 second operation is invoking.
- The class named MyClass.cs will contain a method that will take approximately 5 seconds to complete, I will simulate a method taking 5 seconds by asking the thread to sleep for five seconds using the method
Thread.Sleep()
. The button in your Main Window will invoke this method.
The workflow of this application is as follows:
- App starts (
MainWindow
class in invoked)
- User clicks Button (Named Begin Processing)
- A second thread is created that invokes
MyClass
The Progress Window is displayed
MyClass
completes and it invokes a method in MainWindow
to close the Progress Window.
Based on the description of the workflow, we need the MainClass
to include two methods:
- The first method will carry out the required logic to start the worker thread and display the progress bar, it is called
OnWorkerMethodStart()
.
- The second method will be invoked when the worker thread has completed, it will close the progress bar and collect a
string
from the worker thread, it is called OnWorkerMethodComplete
and accepts a string
as a parameter.
The signature/name of the methods in the MainWindow
are given below. We will fill in the logic in these methods later.
private void button1_Click(object sender, RoutedEventArgs e)
{
OnWorkerMethodStart();
}
private void OnWorkerMethodStart()
{
}
private void OnWorkerMethodComplete(string message)
{
}
Worker Class (MyClass.cs)
Now that we know what we want to happen, we set about creating MyClass
.
The first type we must create is a delegate. You can think of the delegate as a middle man to get to the method 'private void OnWorkerMethodComplete(string message)
' in the MainWindow
class.
To create the delegate, you can copy the name of the target method from MainWindow.cs private void OnWorkerMethodComplete(string message)
and replace 'private
' with 'public delegate
' and rename as required.
public delegate void OnWorkerMethodCompleteDelegate(string message);
We will create the link between the middleman and the MainWindow
later. For now, just be aware that this is what he does. On top of creating our middleman (delegate), we need to create an event to invoke the middleman into action.
The event declaration involves three pieces of data:
- We tell .NET we need an event '
public event
'
- We tell .NET that it should call our middleman (delegate) '
public event OnWorkerMethodCompleteDelegate
'
- And finally, we give it a name '
public event OnWorkerMethodCompleteDelegate OnWorkerComplete;
So we are basically saying this event should call our middleman, which will call the method in MainWindow.cs:
private void OnWorkerMethodComplete(string message)
Finally we create our WorkerMethod
, we simulate code that would take five seconds to complete by adding Thread.Sleep(5000)
and we call the event providing a message saying "The processing is complete
". The event will give it to the middleman and he will give the message to the method in MainWindow.cs:
private void OnWorkerMethodComplete(string message)
class MyClass
{
public delegate void OnWorkerMethodCompleteDelegate(string message);
public event OnWorkerMethodCompleteDelegate OnWorkerComplete;
public void WorkerMethod()
{
Thread.Sleep(5000);
OnWorkerComplete("The processing is complete");
}
}
MainWindow.cs
Now we return to the MainWindow
class. We begin by creating the ProgressBarWindow
object private ProgressBarWindow pbw = null;
. This is the Window we will invoke that contains the progress bar.
In the OnWorkerMethodStart()
method, we create an instance of MyClass
and then we hook up the event in myclass
to our method OnWorkerMethodComplete
.
myC.OnWorkerComplete +=
new MyClass.OnWorkerMethodCompleteDelegate(OnWorkerMethodComplete);
We are telling .NET that when we invoke the OnWorkerComplete
event, we want the delegate in myclass
MyClass.OnWorkerMethodCompleteDelegate
to invoke the method in MainWindow
(OnWorkerMethodComplete)
.
We then create a thread and set the method in WorkerMethod
from MyClass
as the method to invoke on another thread. While this second thread begins, we create ProgressBarWindow
and call the show
method.
ThreadStart tStart = new ThreadStart(myC.WorkerMethod);
Thread t = new Thread(tStart);
t.Start();
As soon as the 5 seconds in the workerMethod
completes, the event is called and it now knows to call the delegate which also knows to call the method private void OnWorkerMethodComplete(string message)
. Finally within this method, we use the .Dispatcher.Invoke
methods on the instance of ProgressBarWindow
and the Label
to close the progress bar window and set the content of the label with the string
value passed from the worker method.
public partial class MainWindow : Window
{
private ProgressBarWindow pbw = null;
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
OnWorkerMethodStart();
}
private void OnWorkerMethodStart()
{
MyClass myC = new MyClass();
myC.OnWorkerComplete +=
new MyClass.OnWorkerMethodCompleteDelegate(OnWorkerMethodComplete);
ThreadStart tStart = new ThreadStart(myC.WorkerMethod);
Thread t = new Thread(tStart);
t.Start();
pbw = new ProgressBarWindow();
pbw.Owner = this;
pbw.ShowDialog();
}
private void OnWorkerMethodComplete(string message)
{
pbw.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
pbw.Close();
}
));
myLabel.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
myLabel.Content = message;
}
));
}
}
Thanks for reading my article. All feedback is very much welcome!!
History
- 9th October, 2011: Revision 1: In the method
OnWorkerMethodStart()
, the line pbw.Show();
was replaced with pbw.ShowDialog();
This ensures that the progress bar window is a modal window, i.e. a child of the MainWindow
. You then cannot invoke the progress window multiple times. The attached source code has also been updated to reflect this change.
- 8th October, 2011: Initial version