Introduction
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 :)
Using the code
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
jump to the summary page without any user action).
Let’s assume that our wizard has three steps (pages):
- Welcome
- Configure the user system (a time consuming operation)
- Summary
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.
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.
Binding the page’s Loaded
event to a class Command
:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding Path=RunOperation}"/> <= 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:
protected BackgroundWorker _operationWorkerThread = null;
protected ICommand _operationWorkCommand = null;
………
this._operationWorkCommand = new CRRelayCommand(() =>
this._operationWorkerThread.RunWorkerAsync());
this._operationWorkerThread = new BackgroundWorker();
this._operationWorkerThread.DoWork += this.DoWork;
this._operationWorkerThread.RunWorkerCompleted += this.RunWorkerCompleted;
In DoWork
I am actually calling a virtual function DoOperation()
, which the inherited classes must
implement.
Main Classes
MainWindow
–
the Wizard main window. CRViewModelWizardMain
– the Wizard UI manager. CRViewWizardMain
– the Wizard main presenter. CRViewModelPageBase
– the ViewModel base class. All the Wizard pages must inherit from this class. CRViewModelOperationPageBase
- the ViewModel Operation base class, inherit from CRViewModelPageBase
. All the
operational classes must inherit from this class.
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>
How to build your own Wizard
- Design our
Wizard layout (
CRViewWizardMain
) and the Wizard’s pages. - For each View
Page add a corresponding class in the ViewModel namespace.
- If a page is
only a presenter inherit from
CRViewModelPageBase
; If a page is operational
inherit from CRViewModelOperationPageBase
. - Operational classes
must override
DoOperation()
to handle the page operation. - Run and try.
Enjoy!
Points of Interest
History
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.