Creating a Busy Indicator in a separate thread in WPF

One of the worst sins a GUI developer can commit is blocking the UI thread. Unfortunately, there will always be times where we must do certain operations on the UI thread. For instance, creating the ItemContainers in an ItemsControl when binding to a very large collection (I know with virtualization turned on this shouldn’t be a very long operation, but I recently ran into this situation with a third party grid control, and it will not be virtualized if you are using GroupDescriptions on your CollectionView).

In these situations, there isn’t much we can do to keep the UI responsive. We have to fall back to showing an indication that, in fact, the UI is just temporarily blocked and will soon become responsive again. If there is a fancy animation happening in your application, like the now ubiquitous spinning circle, then the users know that the app is still at least processing windows messages. Okay, so only windows programmers will know that it is processing windows messages, but at least the app is doing something to show them that it is still alive.

Since I’ve had to solve this problem multiple times for multiple clients, I’ve decided to share how I have done it in WPF. The problem is that we can’t render any UI in the main UI thread, as it is being blocked by the long running operation. In order to create another UI thread in WPF, I’ll point you to this excellent post by Dwayne Need.

I'm only going to use the VisualTargetPresentationSource class from his example, as the functionality that is exposed by the VisualWrapper will be wrapped up with most of the code that was in the code behind of the main window in his example. The idea is to encapsulate the thread starting logic, and just call a method from the new thread that is supplied by the consumer of the control. I’ve wrapped this all up like so:

private class ThreadedVisualHelper  
{  
    private readonly HostVisual _hostVisual = null;
    private readonly AutoResetEvent _sync =
        new AutoResetEvent(false);
    private readonly CreateContentFunction _createContent;
    private readonly Action _invalidateMeasure;

    public HostVisual HostVisual { get { return _hostVisual; } }
    public Size DesiredSize { get; private set; }
    private Dispatcher Dispatcher { get; set; }

    public ThreadedVisualHelper(
        CreateContentFunction createContent,
        Action invalidateMeasure)
    {
        _hostVisual = new HostVisual();
        _createContent = createContent;
        _invalidateMeasure = invalidateMeasure;

        Thread backgroundUi = new Thread(CreateAndShowContent);
        backgroundUi.SetApartmentState(ApartmentState.STA);
        backgroundUi.Name = "BackgroundVisualHostThread";
        backgroundUi.IsBackground = true;
        backgroundUi.Start();

        _sync.WaitOne();
    }

    public void Exit()
    {
        Dispatcher.BeginInvokeShutdown(DispatcherPriority.Send);
    }

    private void CreateAndShowContent()
    {
        Dispatcher = Dispatcher.CurrentDispatcher;
        VisualTargetPresentationSource source =
            new VisualTargetPresentationSource(_hostVisual);
        _sync.Set();
        source.RootVisual = _createContent();
        DesiredSize = source.DesiredSize;
        _invalidateMeasure();

        Dispatcher.Run();
        source.Dispose();
    }
}

This class is a private helper class inside the BackgroundVisualHost class (it is just a FrameworkElement) that can be used directly in XAML. It has a property, CreateContent, that consumers of the control set to a delegate that creates the Visual to display, which gets called in a new thread. It also has an IsContentShowing property that is used to toggle the showing of the threaded UI.

Now that we have an easy way of showing any Visual in a separate thread, let me introduce you to the easy way of showing a busy indicator. If you’ve never heard of a Decorator element, don’t fret. You’ve used them. A lot. Border is one. Decorators have a Child property that is a UIElement, and they “decorate” that UIElement with other content. The Border decorates its Child with lines. We’re going to decorate our child with a busy indicator.

In order to make styling the busy indicator easy, the class has a BusyStyle property:

public static readonly DependencyProperty BusyStyleProperty =  
    DependencyProperty.Register(
    "BusyStyle",
    typeof(Style),
    typeof(BusyDecorator),
    new FrameworkPropertyMetadata(OnBusyStyleChanged));

Since this class is mostly about creating and performing layout on the BackgroundVisualHost, I won’t show its code here, just its usage.

<Window x:Class="Abraham.Heidebrecht.MainWindow"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ctrls="clr-namespace:Abraham.Heidebrecht.Controls"
    Title="Multi-threaded Busy Adorner Sample" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

       <ToggleButton x:Name="toggle" Content="Show Busy Indicator" />

        <ctrls:BusyDecorator Grid.Row="1"
                             IsBusyIndicatorShowing="{Binding IsChecked, ElementName=toggle}">
            <ListBox>
                <ListBoxItem Content="Mercury" />
                <ListBoxItem Content="Venus" />
                <ListBoxItem Content="Earth" />
                <ListBoxItem Content="Mars" />
                <ListBoxItem Content="Jupiter" />
                <ListBoxItem Content="Saturn" />
                <ListBoxItem Content="Uranus" />
                <ListBoxItem Content="Neptune" />
            </ListBox>
        </ctrls:BusyDecorator>
    </Grid>
</Window>  

As you can see, it couldn’t be easier to use once we get it all set up. You can imagine binding the BusyDecorator’s IsBusyIndicatorShowing property to something in your ViewModel when a long running operation is occurring.

Here is the source in all its glory:
MultithreadedBusyAdorner.zip

-AH


View or Post Comments