Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

A reusable ProgressViewModel to observe progress with MVVM

Rate me:
Please Sign up or sign in to vote.
4.94/5 (11 votes)
30 Jan 2012CPOL8 min read 40.3K   737   26   15
UI and code-behind is executed in different threads. Long running processes need asynchronous execution. This artice shows a way of doing this in a ViewModel approach.

Introduction

The introduction of WPF and the Task Parallel Library have made life using C# much easier than ever before. It is time to say goodbye to BackgroundWorker, UI freeze, events, and all those things that do their job but are not the full price, if you want to develop professional applications that are easy to use, responsive, and reliable - while being produced at a competitive price.

In this article, I am going show a ViewModel that can be used to fire off asynchrony process sing and observe its progress towards completion through WPF binding. I name this pattern ProgressViewModel, because it sits between the view and the model and eases the life of both the UI designer and the developer, since both have a set (and yet flexible) interface for connecting background processing with an appealing GUI.

This is a stable solution that can be used with C# 4.0 and later, and we do not have to wait for C# version 5.0 (or have to use the Async CTP yet) to implement background processing without all the worries about communication/thread synchronization/thread context, and so forth.

It is assumed that you already know C# and have a working knowledge of WPF, the Task library, and XAML.

StyleCop

I use StyleCop in my projects to make the code readable in a uniform way. So, you can either download and install StyleCop, or edit/remove the corresponding entries in each .csproj file (if you should get an error when compiling the project):

XML
<Import Project="$(ProgramFiles)\MSBuild\StyleCop\v4.5\StyleCop.Targets" />

Background

I soon hit a few rocks when I started to use WPF. Sometimes the GUI would freeze and sometimes exceptions would fly and leave me clueless for a while. This is yet another attempt to provide a stable software design for getting past these initial problems and hitting the ground running when developing long running processes with WPF.

The Demo Application

The demo application is a program that scans a directory for HTML files and uses the HTML Agility Pack to parse and display the title of each file.

MainWindow.PNG

This article series comes in two parts. You are reading part 1. In this part, we are looking at an application that accepts a path to a file system folder in which HTML files are stored (you can download the attached sample data from Wikipedia (865.75 KB) and point the application to the uncompressed directory if you do not have any HTML laying around on your hard disk).

In any event, point the Path to a location where HTML files are stored and click the Process HTML button. And enjoy the rich progress feedback while processing your request :-)

Using the Code

Re-using the Code

This project can be re-used by including the Progress DLL in another project and implementing a derived version of the ProgressVM class (in this project, that is ProcessHTMLVM). But let us have an overview of how it works before letting it work for us...

The Software Design

In my WPF code, I am now using a layered architecture where I attach an ApplicationViewModel to the DataContext of the MainWindow and put all other ViewModels underneath this ViewModel (expose other ViewModels as properties and methods of the ApplicationViewModel). Let's look at an overview of the class design and explain the call sequence further below.

SoftwareDesign.PNG

The diagram on the left side gives us a bird's eye view of the architecture of the ProgressViewModel.

The heart of it is the ProgressVM class (which lives in a separate DLL to make re-usage a simple matter of referencing it). The ProgressVM class exposes properties that can be used to control and view the progress shown in a progress control (ProgressMin, ProgressMax, and ProgressValue) and whether:

  • a start and stop button should be enabled (CanRunProcess)
  • a progress should be shown (IsProgressVisible)
  • a cancel button should be enabled (CanCancelRunProcess)

The method that does the main work here is RunProcess. This method fires the asynchronous process through a Func<> statement that is handed down to it from the ApplicationViewModel.

But the ApplicationViewModel (AppVM) and ProcessVM are not directly connected. The connecting element is the ProcessHTMLVM class which inherits from ProcessVM. ProcessHTMLVM lives in AppVM as a field called mProcessing and is an exposed property called Processing.

So, when we bind to a ProgressViewModel property in ProcessHTMLVM, we go through the MainWindow->DataContext (see the MainWindow constructor) and the Processing property in the AppVM class.

The ProgressResult class is a container class to hold the result (or pointers to it) that is obtained from the long processing task. We revisit this further below.

The Call Sequence

The following XAML code defines the Process HTML button in the MainWindow class:

XML
<Button Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
            Command="cmd:Main.ProcessHTML"
            CommandParameter="{Binding Path=Path}"
            VerticalAlignment="Center" Grid.Row="2" Margin="3,6,3,6" /> 

The Command bit binds the button to the ProcessHTML command defined in the static Main class in View/Main.cs. It tells WPF, whenever a user clicks this control, then execute this command with the content of the Path property exposed by the AppVM. The Path property of the AppVM contains the current directory path because it is bound to the Text property of the Path textbox:

XML
<TextBox Text="{Binding Path=Path,UpdateSourceTrigger=PropertyChanged}" 
              HorizontalAlignment="Stretch" VerticalAlignment="Center" 
              Grid.Column="1" Grid.Row="1"/> 

Now that's where the input comes from, but let's continue to look at how the command is invoked with that parameter. The actual method to be executed via the ProcessHTML command is declared in the BindCommands method in the code-behind of the MainWindow class.

C#
CommandBindings.Add(new CommandBinding(Main.ProcessHTML,
            (s, e) => this.AppVM.ProcessHTMLDirectory(e,
                      this.prgProgress,
                      this.txtProgressStatus,
                      null, this.lstStatus, null),
            (s, e) => this.AppVM.CanExecute_IfNoProcessRuns(s, e)));

I know this looks awful at first sight, but it is just another way to bind a (to be executed) method to a command. We use a Lambda expression with the CommandBindings class to tell WPF that it should execute:

  • CanExecute_IfNoProcessRuns: determine whether this command is enabled or not, and execute
  • the ProcessHTMLDirectory method to execute this command.

The ProcessHTMLDirectory method in the AppVM class is called with a host of controls as parameters (ProgressBar, etc.) to bind the ViewModel to at run-time. I used this initially because I destroy the ProcessHTMLVM object each time I start a new process and I thought that I would have to dynamically re-bind (with code).

It turns out that the binding in the XAML is enough, because WPF re-binds correctly when the Processing property is changed. So, I could either remove the property binding in the XAML (because there is a binding in the code-behind) or remove the call to BindStatusBarItems in the ProcessHTMLDirectory method (which is more likely what I would do).

But I opted to leave things as they are so people can actually see how equivalent expressions for binding in code-behind and XAML are.

At any rate, the ProcessHTMLDirectory method sets up:

  • a new object of type ProgressVM (or more specifically ProcessHTMLVM) in the BindStatusBarItems method,
  • registers a callback method ProcessHTML_Results that is executed when processing is finished (with or without errors),
  • calls SetProgressVisibility to switch progress controls to Visible from Collapsed state,
  • puts the path string into a Dictionary object and hands it over to the RunProcess method in the ProcessHTMLVM class.

I am using the Dictionary approach to pass parameters in and get results out. Because, I think this is simple enough to deal with while I do not have to change the signature of the RunProcess method. This way, I can apply the same method of instantiating the async ProgressViewModel framework over and over again, while doing completely different things with each implementation...

One important parameter of the RunProcess method call is the ProcessHTMLDirectory bit. This is actually a method handed over to RunProcess. RunProcess uses this method to do the asynchronous processing via the Func<> delegate approach. The code inside RunProcess shows that it:

  • initiates a Task class via its factory
  • creates a new CancelToken to let the executing process know when a user would like to cancel a running process, and
  • starts the actual processing (ProcessHTMLVM.ProcessHTMLDirectory) via execFunc(callParameters)

Now looking into ProcessHTMLVM.ProcessHTMLDirectory, we can see how the Dictionary call parameter is unpacked and the HTML processing is implemented there. Setting values such as this.ProgressMax inside that method, -are, via AppVM and binding, reported back to the progress controls of the UI.

The result of the ProcessHTMLDirectory method is passed back to ProcessHTML_Results in AppVM via the this.callResults.Add statement at the bottom of the class.

The ProcessHTMLVM class defines keyString values (in the fields region) to be used for the parameter and a result Dictionary and the ProcessHTMLDirectory method to be executed. The ProcessName property can be used to output a meaningful string to the user if we need to display a descriptive name for the running background process. Everything else is inherited from ProgressVM.

Summary

Overviewing the sequence of executing methods described above, we can see the command being initiated in the MainWindow, being converted in a method call in the AppVM, being processed in ProcessHTMLVM, and being passed back to ProcessHTML_Results in AppVM.

Noteworthy is also the Cancel command which executes the CancelProcessing method in the AppVM class. That call is passed on to the ProgressVM object (if there is any) and inside that, CancelToken is set to scream at the running process: Get out of here!

Points of interest

I discovered this pattern writing an application that processes about 20 commands, always using the same template, I soon found it easy and relaxing writing responsive and reliable applications. I hope it can help others to concentrate on their task at hand and not having to worry about all these tiny techniques and twists cast into the Progress DLL.

Part two of this article will develop the idea shown here a little bit further. We are going to expose properties for viewing multiple progress values when processing complex algorithms.

Please comment and vote on this to let me know what you think about this.

History

  • 20 January 2012: Initial creation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
The Windows Presentation Foundation (WPF) and C# are among my favorites and so I developed Edi

and a few other projects on GitHub. I am normally an algorithms and structure type but WPF has such interesting UI sides that I cannot help myself but get into it.

https://de.linkedin.com/in/dirkbahle

Comments and Discussions

 
GeneralMy vote of 3 Pin
Dean Oliver31-Jan-12 5:43
Dean Oliver31-Jan-12 5:43 
GeneralMy vote of 3 Pin
azweepay30-Jan-12 20:01
azweepay30-Jan-12 20:01 
GeneralRe: My vote of 3 Pin
Dirk Bahle31-Jan-12 4:34
Dirk Bahle31-Jan-12 4:34 
Can you be more specific please. Would you rather use converters to deal with visibility or what is it that bothers you. I am not an MVVM expert but all articles I have seen so far state that the ViewModel has knowledge of the view and the model. This article:

http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090048[^]

even suggests special viewmodels for certain controls (Workspace, MainWindow etc).
GeneralMessage Removed Pin
31-Jan-12 6:21
professionalN_tro_P31-Jan-12 6:21 
GeneralRe: My vote of 3 Pin
azweepay31-Jan-12 20:17
azweepay31-Jan-12 20:17 
GeneralRe: My vote of 3 Pin
Dirk Bahle3-Feb-12 23:31
Dirk Bahle3-Feb-12 23:31 
Questionvery nice Pin
BillW3323-Jan-12 5:39
professionalBillW3323-Jan-12 5:39 
AnswerRe: very nice Pin
Dirk Bahle23-Jan-12 8:33
Dirk Bahle23-Jan-12 8:33 
QuestionHey Pin
Sacha Barber20-Jan-12 6:33
Sacha Barber20-Jan-12 6:33 
AnswerRe: Hey Pin
Dirk Bahle23-Jan-12 8:30
Dirk Bahle23-Jan-12 8:30 
GeneralRe: Hey Pin
Sacha Barber23-Jan-12 9:02
Sacha Barber23-Jan-12 9:02 
GeneralMessage Removed Pin
31-Jan-12 6:24
professionalN_tro_P31-Jan-12 6:24 
GeneralRe: Hey Pin
Dirk Bahle31-Jan-12 9:06
Dirk Bahle31-Jan-12 9:06 
GeneralMessage Removed Pin
31-Jan-12 9:11
professionalN_tro_P31-Jan-12 9:11 
GeneralRe: Hey Pin
Dirk Bahle31-Jan-12 11:27
Dirk Bahle31-Jan-12 11:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.