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