WPF Round Table Part 2: Multi UI Threaded Control – Fixes

Introduction

Click here to view the source code

Here are the past articles in the WPF Round Table Series:

In my last post I discussed a control I made that allowed for a user to create inline XAML on different UI threads. Today, I am going to discuss a couple of the pitfalls I ran into when attempting to resolve an issue a user asked about

FrameworkTemplates

So, someone asked about how to solve a particular issue with having 4 controls with busy indicators loading all on separate threads. As I was attempting to construct a solution my first instinct was to simply use the ThreadSeparatedStyle property and set the Style’s Template property with the look you want, sort of like this:

<Style TargetType="{x:Type Control}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Control}">
                <multi:BusyIndicator IsBusy="True">
                    <Border Background="#66000000">
                        <TextBlock Text="Random Text" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Border>
                </multi:BusyIndicator>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Suddenly, I was hit with a UI thread access exception when attempting to do this. The problem arises from how WPF allows users to design FrameworkTemplates. WPF instantiates the templates immediately, which will cause threading issues when attempting to access this setter value on our separate UI thread. The key to solving this is by deconstructing the template into a thread safe string by using XAML serialization. First we will grab any FrameworkTemplates from the style:

var templateDict = new Dictionary<DependencyProperty, string>();
foreach ( var setterBase in setters )
{
    var setter = (Setter)setterBase;
    var oldTemp = setter.Value as FrameworkTemplate;
    // templates are instantiated on the thread its defined in, this may cause UI thread access issues
    // we need to deconstruct the template as a string so it can be accessed on our other thread
    if ( oldTemp != null && !templateDict.ContainsKey( setter.Property ) )
    {
        var templateString = XamlWriter.Save( oldTemp );
        templateDict.Add( setter.Property, templateString );
    }
}

Then, while recreating our Style on the newly created UI thread, we reconstruct the template:

foreach ( var setterBase in setters )
{
    var setter = (Setter)setterBase;
    // now that we are on our new UI thread, we can reconstruct the template
    string templateString;
    if ( templateDict.TryGetValue( setter.Property, out templateString ) )
    {
        var reader = new StringReader( templateString );
        var xmlReader = XmlReader.Create( reader );
        var template = XamlReader.Load( xmlReader );
        setter = new Setter( setter.Property, template );
    }
    newStyle.Setters.Add( setter );
}

Now we are able to design our UI thread separated control inline our main Xaml and also any FrameworkTemplates that are defined within.

XAML Serialization Limitations

I actually ran into another error when attempting to insert my custom UserControl into the UI thread separated Style’s template. It involved a ResourceDictionary duplicate key error. This problem absolutely dumbfounded me; not only in trying to understand why the same resource would try to be defined twice, but also how can there be duplicates on a newly created UI thread. After racking my head for hours to come up with a work around solution I eventually found out the direct cause of the error in question. It had to do with how the XamlWriter class serializes the given XAML tree. To give you an idea let’s say we have our ThreadSeparatedStyle defined like this:

<Style TargetType="{x:Type Control}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Control}">
                <Border Width="100" Height="50" VerticalAlignment="Bottom">
                    <Border.Resources>
                        <converters:ColorValueConverter x:Key="ColorValueConverter"/>
                    </Border.Resources>
                    <Border.Background>
                        <SolidColorBrush Color="{Binding Source='Black', Converter={StaticResource ColorValueConverter}}"/>
                    </Border.Background>
                    <TextBlock Text="Random Text" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

When Xaml.Save attempts to serialize the ControlTemplate here is our string result:

<ControlTemplate TargetType="Control"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:cpc="clr-namespace:Core.Presentation.Converters;assembly=Core"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Border Width="100" Height="50" VerticalAlignment="Bottom">
        <Border.Background>
            <SolidColorBrush Color="#FF000000" />
        </Border.Background>
        <Border.Resources>
            <cpc:ColorValueConverter x:Key="ColorValueConverter" />
        </Border.Resources>
        <TextBlock Text="Random Text" Foreground="#FFFFFFFF" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
</ControlTemplate>

Now, if we decided to wrap this into a UserControl, called RandomTextUserControl, it may look like this:

<UserControl x:Class="MultiUiThreadedExample.RandomTextUserControl"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:converters="clr-namespace:Core.Presentation.Converters;assembly=Core">
    <UserControl.Resources>
        <converters:ColorValueConverter x:Key="ColorValueConverter"/>
    </UserControl.Resources>
    <Border Width="100" Height="50" VerticalAlignment="Bottom">
        <Border.Background>
            <SolidColorBrush Color="{Binding Source='Black', Converter={StaticResource ColorValueConverter}}"/>
        </Border.Background>
        <TextBlock Text="Random Text" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
    </Border>
</UserControl>

When we replace our current XAML with this control we will receive the ResourceDictionary XamlParseException because it is trying to include ‘ColorValueConverter’ more than once. If we go back to our Xaml.Save result we will find our culprit:

<ControlTemplate TargetType="Control"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:mute="clr-namespace:MultiUiThreadedExample;assembly=MultiUiThreadedExample"
                 xmlns:cpc="clr-namespace:Core.Presentation.Converters;assembly=Core"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <mute:RandomTextUserControl>
        <mute:RandomTextUserControl.Resources>
            <cpc:ColorValueConverter x:Key="ColorValueConverter" />
        </mute:RandomTextUserControl.Resources>
        <Border Width="100" Height="50" VerticalAlignment="Bottom">
            <Border.Background>
                <SolidColorBrush Color="#FF000000" />
            </Border.Background>
            <TextBlock Text="Random Text" Foreground="#FFFFFFFF" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </mute:RandomTextUserControl>
</ControlTemplate>

As you can see, XamlWriter.Save is actually including our parent level resources from RandomTextUserControl. This will cause duplication issue since it will attempt to add the resources displayed here plus the ones already defined inside RandomTextUserControl. The reason is because XamlWriter tries to keep the result self-contained. Meaning, the final result will be a single page XAML tree. Unfortunately, the process tends to add any referenced resources that may come from the overall application. This limitation, along with others, are actually documented by Microsoft. So, the solution here is to either put all your resources into the first content elements resources property or define the design of your control using a template, like this:

<UserControl x:Class="MultiUiThreadedExample.RandomTextUserControl"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:converters="clr-namespace:Core.Presentation.Converters;assembly=Core"
             xmlns:multiUiThreadedExample="clr-namespace:MultiUiThreadedExample">
    <UserControl.Template>
        <ControlTemplate TargetType="{x:Type multiUiThreadedExample:RandomTextUserControl}">
            <ControlTemplate.Resources>
                <converters:ColorValueConverter x:Key="ColorValueConverter"/>
            </ControlTemplate.Resources>
            <Border Width="100" Height="50" VerticalAlignment="Bottom">
                <Border.Background>
                    <SolidColorBrush Color="{Binding Source='Black', Converter={StaticResource ColorValueConverter}}"/>
                </Border.Background>
                <TextBlock Text="Random Text" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
            </Border>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

I actually prefer this method since it reduces an unnecessary ContentPresenter from being created and allows for more seamless TemplateBinding with the parent and Triggering.

One thought on “WPF Round Table Part 2: Multi UI Threaded Control – Fixes

Leave a reply to Peter Hartman Cancel reply