The article aims to demonstrate how to implement a Time Consuming Operation when using WPF Wizard, based on
the MVVM design pattern (i.e., using the Setting Home Network
Connection wizard in Windows).
I must add that the attached code is only for demonstration purpose, it is far from being complete for production. However, it can be a good starting point if you
need to perform a consuming operation while running a WPF wizard.
Searching the web for WPF Wizard, I came across a great article Creating an
Internationalized Wizard in WPF (by Josh Smith,
Karl Shifflett), which gave me a great starting point,
though I needed to implement also a scenario were one of the wizard steps need to run more than
a few seconds, meaning perform a long operation. I couldn’t
find an example on the web, I had to get my hands dirty and implement it myself.
If you don’t have a background in WPF or MVVM you must first read the article Creating an
Internationalized Wizard in WPF where I took the code from. Those who have some background can probably jump right in.
I stripped off the Creating an
Internationalized Wizard in WPF code to the minimum, so I could concentrate on my main purpose: a working
WPF Wizard which has a time
consuming operation. At this point I have no interest in a fancy UI, each page displaying its title is perfectly sufficient.
For the time consuming operation I wanted to add a progress bar, I really like the round progress bars which are mostly common on the web. I found in CodeProject a nice and straightforward implementation: Better WPF
Circular Progress Bar (by Sacha Barber), which saved me a lot of time.
In this post I am only going to concentrate on how to add a time consuming operation to
the WPF Wizard, rather than how the WPF MVVM Wizard works (this you can
find in the above link).
Saying that I will explain in detail at the end how you can use the code for
easily building your own Wizard.
I believe there are other ways to achieve the task, but I found the suggested solution pretty straightforward to understand and to implement.
I tried to reduce the code to the minimum, allowing the main goal of this post to be presented clearly, thus I am only going to explain the main points.
If you
want to understand how things work you will have to dig into the code. The code at
the beginning might be confusing, especially if you are new to WPF, but I think
it will be worthwhile at the end.
The code was built in VS 2010 SP1.
Hope you find it useful
We all know that Wizard has three buttons: Back, Next/Finished, and Cancel. The Next button navigates to the Next
wizard’s page up to the final page (most wizards actually
Let’s assume that our wizard has three steps (pages):
In the above scenario, when the user clicks Next in the Welcome page, he/she
is actually accepting the second step “Configure the user system”, the step that
we need to do some work (i.e., checking system requirement, downloading some files….).
The long operation only happens in stage 2 so we don’t want to add to the code
next[i].DoWork(), causing some operations to happen on each page (actually we
can, but it looks….not great, agree?). We want however to bind the long operation only to the second page.
next[i].DoWork()
In MVVM every View Page has a corresponding class in the ViewModel namespace; therefore we can bind the
page’s Loaded event to a class Command, which will
cause, at the right moment, to launch the long operation, in our example – the Configure
operation.
Loaded
Command
Binding the page’s Loaded event to a class Command:
<i:Interaction.Triggers>
<i:EventTrigger
<i:InvokeCommandAction <= binding command
</i:EventTrigger>
</i:Interaction.Triggers>
Binding between the two is great but we also need to launch the operation. Of course, we cannot launch it on the UI
thread causing the UI to freeze, we must launch
it on a different thread, allowing for example to cancel the Operation in the middle (if it possible).
Using BackgroundWorker is a good option; it will allow integrating the
operation with the Binding Command. As follows:
BackgroundWorker
protected BackgroundWorker _operationWorkerThread = null;
protected ICommand _operationWorkCommand = null;
// the command which bind to the relevant view. RunOperation in the example
………
this._operationWorkCommand = new CRRelayCommand(() =>
this._operationWorkerThread.RunWorkerAsync()); //Start the thread on binding
this._operationWorkerThread = new BackgroundWorker();
this._operationWorkerThread.DoWork += this.DoWork; // The working thread in action
this._operationWorkerThread.RunWorkerCompleted += this.RunWorkerCompleted; //the working thread completed
In DoWork I am actually calling a virtual function DoOperation(), which the inherited classes must
implement.
DoWork
DoOperation()
MainWindow
CRViewModelWizardMain
CRViewWizardMain
CRViewModelPageBase
CRViewModelOperationPageBase
It is important to remember the connection between the
Page view and the ViewModel object. In CRViewWizardMain.xaml we need to
declare:
<DataTemplate DataType="{x:Type viewModel:CRViewModelXXXPage}">
<view:CRViewXXXPage/>
</DataTemplate>.