As you may know, I am the author of a WPF control suite call the Extended WPF Toolkit.  I also spend tons of time on various forums helping people solve problems with their WPF applications.  Lately there have been a number of questions regarding the BusyIndicator control.  The majority of questions deal with using it in a multi-threaded environment.  So I thought the best way to address the masses is to write up a post demonstrating the proper use of the BusyIndicator control.

Let’s begin by defining what the BusyIndicator is used for.  The BusyIndicator is used to give an application user a visual indicator that a long running process is occurring.  So from this definition we can make two assumptions. One; There must be a mechanism for notifying the BusyIndicator when the long running process begins and when it ends.  Two; the long running process will have to occur on a separate thread as to not block the UI.

Anatomy of the BusyIndicator

The first thing you need to do when using any control inside the Extended WPF Toolkit is add a namespace declaration to the consuming view.

<Window x:Class="BusyIndicatorDemo.MainWindow"
        xmlns:extToolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit/extended"
       

The BusyIndicator is a ContentControl.  What this means is that the BusyIndicator can contain a single child element within it’s open and closing tags.  For example:

<extToolkit:BusyIndicator>
    <Grid>
        <Button>Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

Looking at this code snippet you can see that the Grid is the BusyIndicator’s Content.  If you were to place anything outside of this Grid element you would receive an exception.

The BusyIndicator has two visual states:

Not Busy:

image

Busy:

image

Toggling between these states is as simply as setting a single property called IsBusy.

<extToolkit:BusyIndicator IsBusy="True" >
    <Grid>
        <Button>Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

When the BusyIndicator.IsBusy property is set to True, the BusyIndicator becomes visible and prevents any user interaction with the BusyIndicator’s Content.  So any elements that you have defined within the Content of the BusyIndicator will be disabled.  When the IsBusy property is set to False, the Content re-enables and user interaction can continue.

The Common Problem

The most common problem I see when people are having trouble using the BusyIndicator is that they are doing everything on the UI thread.  Let’s look at an example:

<extToolkit:BusyIndicator x:Name="_busyIndicator" >
    <Grid>
        <Button Click="StartProcess">Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

The event handler for the button looks like this:

private void StartProcess(object sender, RoutedEventArgs e)
{
    //show BusyIndicator
    _busyIndicator.IsBusy = true;
    
    //long running process
    for (int i = 0; i < 100; i++)
    {
        System.Threading.Thread.Sleep(50);
    }

    //hide BusyIndicator
    _busyIndicator.IsBusy = false;
}

Now the assumption is that first the BusyIndicator will be shown, then the long running process will occur, and lastly the BusyIndicator will be hidden.  To most people’s surprise it doesn’t work.  The BusyIndicator never shows and the UI is not responsive.  Do you see the problem?  The problem is that everything is running on the UI thread.

Solution

As we have already stated, the long running process must occur on a separate thread.  The UI thread needs to stay responsive while the process is running.  The most common method to place a long running process on a separate thread is to use the BackgroundWorker.

Let’s modify the event handler to utilize a BackgroundWorker.

private void StartProcess(object sender, RoutedEventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    //this is where the long running process should go
    worker.DoWork += (o, ea) =>
        {
            //no direct interaction with the UI is allowed from this method
            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(50);
            }
        };
    worker.RunWorkerCompleted += (o, ea) =>
        {
            //work has completed. you can now interact with the UI
            _busyIndicator.IsBusy = false;
        };
    //set the IsBusy before you start the thread
    _busyIndicator.IsBusy = true;
    worker.RunWorkerAsync();
}

Examine the code.  First we create an instance of a BackgroundWorker and add delegates for the DoWork and RunWorkerCompleted events.  The DoWork hander is where the long running process should go.  NO DIRECT INTERACTION WITH THE UI IS ALLOWED.  The RunWorkerCompleted handler is code that will run when the long running process has completed.  This is where you can interact with the UI again, meaning that this would be a perfect place to set the IsBusy property to False.  After the long running process has finished.  Now pay special attention to where we are calling RunWorkerAsync.  Just before that call, we are setting the IsBusy property to true.  Then the call to RunWorkerAsync is made.  This will show the BusyIndicator and then start the long running process.  When the process is complete the BusyIndicator is hidden again.  Perfect!

Common Problem 2

The next common problem is dealing with using data that is generated on the background thread in the UI.  Well if you read my post on multi-threading then you should already know how to solve this one.  If you haven’t, I will show you again.  This scenario requires that our UI have a ListBox control that is populated with items on a background thread.  While the items are being populated, the BusyIndicator should show the user something is happening and that they need to be patient.  Let’s modify our UI and our code behind to support his new scenario:

<extToolkit:BusyIndicator x:Name="_busyIndicator" >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox x:Name="_listBox" />
        <Button Grid.Row="1" Click="StartProcess">Start Process</Button>
    </Grid>
</extToolkit:BusyIndicator>

private void StartProcess(object sender, RoutedEventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += (o, ea) =>
        {
            _busyIndicator.IsBusy = true;

            List<String> listOfString = new List<string>();
            for (int i = 0; i < 100; i++)
            {
                listOfString.Add(String.Format("Item: {0}", i));
            }

            //BAD MOJO
            _listBox.ItemsSource = listOfString;
            _busyIndicator.IsBusy = false;
        };            
    worker.RunWorkerAsync();
}

This is similar to what the problem cases look like.  This code will most definitely fail.  Remember, you cannot access any UI elements on a separate thread.  Now let’s modify this code to make it run correctly.

Solution

private void StartProcess(object sender, RoutedEventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += (o, ea) =>
        {
            List<String> listOfString = new List<string>();
            for (int i = 0; i < 10000000; i++)
            {
                listOfString.Add(String.Format("Item: {0}", i));
            }

            //use the Dispatcher to delegate the listOfStrings collection back to the UI
            Dispatcher.Invoke((Action)(() => _listBox.ItemsSource = listOfString));
        };
    worker.RunWorkerCompleted += (o, ea) =>
    {
        _busyIndicator.IsBusy = false;
    };
    _busyIndicator.IsBusy = true;
    worker.RunWorkerAsync();
}

First we had to remove any reference to a UI element from the DoWork handler.  Now in order to propagate the generate list of strings back to the ListBox we enlist the help of the Dispatcher.  The Dispatcher will take the collection of strings, and in a thread safe way, give them to the ListBox for consumption.  Our BusyIndicator will be shown while the collection is being generated, and then be hidden when the process has been completed.

That about wraps it up.  These are the most common troubles developers have had using the BusyIndicator control.  If you have different problems that you haven’t been able to solve, feel free to post your question in the Discussions page of the project site.  You never know, it may be worth blogging about.

Brian Lagunas

View all posts

2 comments

  • You need to update this as the actual import is now : xmlns:extToolkit=”http://schemas.xceed.com/wpf/xaml/toolkit”

    • Thanks for the tip, but this post is 4 years old, and I no longer manage this project. Xceed now manages the project, and I really don’t want to spend the time updating all my posts. Especially since every time I try, the source code formatting gets all screwed up.

Follow Me

Follow me on Twitter, subscribe to my YouTube channel, and watch me stream live on Twitch.