.NET - WPF ProcessingControl and BackgroundWorker
- Date:
- Author: Stefan Cruysberghs
Last week I was trying some techniques to create responsive user interfaces with WPF. Creating a BackgroundWorker object is an obvious choice because it makes threading really easy to use. I was also looking for a solution to provide visual feedback for the user while updating some user controls. On the CodeProject website I found a nice ProcessingControl from Yaakov Davis. So this article, which will reuse my WPF Outlook contacts demo application (see article Display Outlook contact pictures in WPF application), will show how to combine these techniques.
This is what the result will look like :
ProcessingContentControl
You can download the sources of this Yaakov Davis' control on the CodeProject website : http://www.codeproject.com/KB/WPF/ProcessingContentControl.aspx
The ProcessingContentControl is a container control which can host other WPF elements. When the IsContentProcessing property is True, an overlay layer will appear. It will block all interaction with the host element and display an animated circle.
1) Add a reference to Yaakov.Controls assembly.
2) Add a ResourceDictionary in the Application Resources. So open the App.xaml and add the highlighted line.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Yaakov.Controls;Component/ProcessingContentControlResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
3) Add a XML namespace prefix called process in your WPF Window or UserControl. This prefix has to refer to the Yaakov.controls namespace and assembly.
<Window x:Class="WpfOutlook.OutlookContacts"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfOutlook"
xmlns:outlook="clr-namespace:Microsoft.Office.Interop.Outlook;assembly=Microsoft.Office.Interop.Outlook"
xmlns:process="clr-namespace:Yaakov.Controls;assembly=Yaakov.Controls">
4) Add a
ProcessingContentControl object in your XAML code and give it a name. This control will host other WPF elements.
<process:ProcessingContentControl Name="processingControl">
<ListBox Name="listBoxContacts" MouseDoubleClick="listBoxContacts_MouseDoubleClick">
</ListBox>
</process:ProcessingContentControl>
Now you can set the IsContentProcessing property to True or False in your code to show the animated progress view.
BackgroundWorker
WPF provides an ObjectDataProvider class which supports asynchronous data querying but this class has several disadvantages. In my opinion it is a lot better to use the BackgroundWorker class and to write some extra code.
The BackgroundWorker class was introduced in .NET 2.0. and WinForms developers will probably already be familiar with this component. It can also easily be used in WPF applications. The BackgroundWorker class implements a thread which will improve responsiveness in your UI and it supports cancelling and displaying the progress.
Several articles about this control can be found on the internet. I will refer to 2 interesting ones :
- Andrew D. Weiss : http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
- Michael Livshitz : http://www.c-sharpcorner.com/UploadFile/LivMic/BGWorker07032007000515AM/BGWorker.aspx
In this example I will show the most simple implementation of the BackgroundWorker class.
1) Create a new BackgroundWorker object in code.
2) Implement a DoWork event as an anonymous method (=inline delegate). This event will process the data and return the result. In this example a LINQ to Objects query on the contact items of Outlook will be executed . LINQ uses deferred execution. So make sure the query will be executed in the thread and not after the thread when the query will be passed to the listbox. In this example immediate execution is being forced by calling the ToList() method.
3) Implement a RunWorkerCompleted event which will use the result of the thread and update the UI. It should also stop the process animation so set the IsContentProcessing property of the ProcessingControl to False.
4) Call the RunWorkerAsync() method to start the thread and set IsContentProcessing to True to start the animation and block the interaction with the ListBox.
private BackgroundWorker worker;
void execute_Execute(object sender, RoutedEventArgs e)
{
string searchName = textBoxName.Text;
worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
OutlookItems outlookItems2 = new OutlookItems();
var contacts = from contact in outlookItems2.ContactItems
where contact.JobTitle == "Testdata"
select contact;
if (!string.IsNullOrEmpty(name))
contacts = contacts.Where(c => c.FullName.Contains(searchName));
args.Result = contacts.ToList();
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
if (args.Error == null)
{
listBoxContacts.ItemsSource = (args.Result as IEnumerable<Microsoft.Office.Interop.Outlook.ContactItem>);
}
processingControl.IsContentProcessing = false;
};
worker.RunWorkerAsync();
processingControl.IsContentProcessing = true;
}
In this example the events are implemented as anonymous methods. Of course you can also create event handlers and attach them to the events of the BackgroundWorker class. In stead of attaching and assigning all this in code, you can also declare everything as a resource in your XAML file.
So next example does just the same but with another technique.
1) Declare a BackgroundWorker object and attach the events in your XAML file. Do not forget to declare a reference to the System.ComponentModel namespace.
<Window x:Class="WpfOutlook.OutlookContacts"
...
xmlns:componentmodel="clr-namespace:System.ComponentModel;assembly=System">
<Window.Resources>
<componentmodel:BackgroundWorker x:Key="backgroundWorker"
DoWork="BackgroundWorker_DoWork"
RunWorkerCompleted="BackgroundWorker_RunWorkerCompleted">
</componentmodel:BackgroundWorker>
</Window.Resources>
2) Switch to your C# (or VB.NET) code. The FindResource() method can be used to access our backgroundWorker resource.
3) Be carefull not to access UI elements in the thread (=in the DoWork event). So in this case you have to get the name from the TextBox before starting the thread. Call the RunWorkerAsync() method.
4) The implementation of the events is still the same.
private BackgroundWorker worker;
string searchName;
void execute_Execute(object sender, RoutedEventArgs e)
{
worker = (BackgroundWorker)this.FindResource("backgroundWorker");
searchName = textBoxName.Text;
worker.RunWorkerAsync();
processingControl.IsContentProcessing = true;
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs args)
{
...
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs args)
{
...
}
Of course these are just the basics for creating responsive user interfaces with WPF. There are a lot of other topics which you have to consider; using virtualized panels with large data, sharing resources, using freezable objects, using WPF profilers to analyze run-time behavior, ...
I hope you like this example. If you have any remarks or suggestions, please let me know.