Design Time WPF XAML Resource Dictionaries

True to the motto “oldie but a goldie” I have a simple solution for a typical problem arising in a modular WPF application: If you have a setup where your Resource Dictionary is located in another project, the designer is very likely to be ignorant about its contents. Merging the dictionary into your App.xaml Resource node will handle the runtime situation correctly, but VS is likely to fail in showing the resource at design time as desired. That is, Resource usages gets red squiggles and Intellisense fails.

You might get the idea that this is the solution:

<UserControl x:Class="MyApp.MyView"
             ...>
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MyThemeLib;component/Themes/Generic.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
</UserControl>

Bad approach. You are basically creating a copy of the Resource Dictionary at the UserControls’s Resource node. This will get you in performance and memory trouble in a large application. Just imagine that this UserControl is used as a DataTemplate in large list.

A simple solution that avoids most of the runtime penality looks like this:

<UserControl x:Class="MyApp.MyView"
             ...
             xmlns:lib="clr-namespace:MyLib;assembly=MyLib">
    <UserControl.Resources>
        <lib:DesignTimeResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MyThemeLib;component/Themes/Generic.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </lib:DesignTimeResourceDictionary>
    </UserControl.Resources>    
</UserControl>

And the implementation of DesignTimeResourceDictionary:

public class DesignTimeResourceDictionary : ResourceDictionary
{
    private readonly ObservableCollection<ResourceDictionary> _noopMergedDictionaries = new NoopObservableCollection<ResourceDictionary>();

    private class NoopObservableCollection<T> : ObservableCollection<T>
    {
        protected override void InsertItem(int index, T item)
        {
            // ignores: base.InsertItem(index, item);
        }
    }

    public DesignTimeResourceDictionary()
    {
        var fieldInfo = typeof(ResourceDictionary).GetField("_mergedDictionaries", BindingFlags.Instance | BindingFlags.NonPublic);
        if (fieldInfo != null)
        {
            fieldInfo.SetValue(this, _noopMergedDictionaries);
        }
    }
}

The weird thing is the strange behavior of the Visual Studio designer. The code above will do the following: At runtime it will not add the Resource Dictionary inside a Resource Dictionary MergeDictionaries section by injecting a Noop-Collection that ignores additions. VS, however, happily disregards this runtime behavior and will show the resource as defined in the referenced file. The VS designer treats a couple of things very differently at design time. This is one of them.

Please note, that the solution is not ideal. First, it uses reflection over a private field (but hey, it’s WPF and thus very unlikely to change). Second, the Resource Dictionary is still processed (materialized) at runtime. However, it is not kept in memory and is not expanding the resource dictionary tree.


Update 2016-10-10: VS2015 Update 3.

As noted by a reader, the workaround seems to fail in VS2015 Update 3. Here is a quick fix for the issue:

Please cache the DesignerProperties.GetIsInDesignMode value in your implementation to avoid frequent checks in your production code.

public class DesignTimeResourceDictionary : ResourceDictionary
{
    private readonly ObservableCollection<ResourceDictionary> _noopMergedDictionaries = new NoopObservableCollection<ResourceDictionary>();

    private class NoopObservableCollection<T> : ObservableCollection<T>
    {
        protected override void InsertItem(int index, T item)
        {
            // Only insert items while in Design Mode (VS is hosting the visualization)
            if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                base.InsertItem(index, item);
            }
        }
    }

    public DesignTimeResourceDictionary()
    {
        var fieldInfo = typeof(ResourceDictionary).GetField("_mergedDictionaries", BindingFlags.Instance | BindingFlags.NonPublic);
        if (fieldInfo != null)
        {
            fieldInfo.SetValue(this, _noopMergedDictionaries);
        }
    }
}
 
comments powered by Disqus