Search code examples
c#wpfxamlresourcedictionary

Merging WPF ResourceDictionary at Runtime


I have a WPF user control which defines some default styles and images which I would like to be able to override at runtime with custom styles and images contained in resource assembly.

The styles and images are contained in a ResourceDictionary called DefaultResources.xaml in an assembly named Olbert.JumpForJoy.DefaultResources:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    x:Name="J4JResources">

    <BitmapImage x:Key="J4JMessageBoxImage" UriSource="assets/j4jmsgbox.png" />
    <BitmapImage x:Key="J4JWizardImage" UriSource="assets/j4jtransparent.png" />
    <Color x:Key="J4JButton0Color">#bb911e</Color>
    <Color x:Key="J4JButton1Color">#252315</Color>
    <Color x:Key="J4JButton2Color">#bc513e</Color>
    <Color x:Key="J4JButtonHighlightColor">Orange</Color>

</ResourceDictionary>

To provide this to an app, I add a nuget package created when the resource project is compiled to the application where the custom resources are to be used. I've confirmed the dll gets added to the targeted bin directory.

I try to load the custom resource assembly in the App.xaml.cs file, within the OnStartup() method:

public partial class App : Application
{
    public const string ResourceDll = "Olbert.JumpForJoy.DefaultResources";

    protected override void OnStartup( StartupEventArgs e )
    {
        try
        {
            var resDllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{ResourceDll}.dll");

            if (File.Exists(resDllPath))
            {
                var resAssembly = Assembly.LoadFile(resDllPath);
                var uriText = $"pack://application:,,,/{resAssembly.GetName().Name};component/DefaultResources.xaml";

                ResourceDictionary j4jRD = new ResourceDictionary {
                        Source = new Uri(uriText)
                    };

                Resources.MergedDictionaries.Add(j4jRD);
            }
        }
        catch (Exception ex)
        {
        }
    }
}

But an exception gets thrown when the ResourceDictionary is created. The message is "Cannot locate resource 'defaultresources.xaml'".

I've tried quite a few tweaks to the uri definition to solve this, none of which have worked. The resource assembly is versioned, but whether or not I include a specific version in the uri definition I get the same error.

If there's a different way of merging an optional resource assembly into a WPF project I'd love to hear about it. An answer to my specific problem would be appreciated, too :)

Update

If I try to do this through app.xaml:

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="pack://application:,,,/Olbert.JumpForJoy.DefaultResources;component/DefaultResources.xaml" />
    <ResourceDictionary Source="NotifyIconResources.xaml"/>
</ResourceDictionary.MergedDictionaries>

I get a design-time error "An error occurred while finding the resource dictionary..."

Calling resAssembly.GetManifestResourceNames() in my original approach showed that the resources do exist in the custom assembly. But they appear "under" the assembly's default namespace:

Olbert.JumpForJoy.WPF.DefaultResources.xaml

Which makes me wonder if I need to specify that namespace, somehow, in defining the Uri.


Solution

  • For the sake of others struggling with this problem, here's the solution I dicovered: the problem was that the default namespace for the external resource assembly must be the same as the name of that assembly's DLL. If the names are different the pack Uri syntax fails.

    I've documented this more fully on my blog at http://jumpforjoysoftware.com/2017/06/the-pain-of-shared-wpf-resources/