How can I chain Resource Dictionaries that are externally loaded from disk, not included in project or assembly?

StackOverflow https://stackoverflow.com/questions/21560235

Question

I've got a ResourceDictionary in a xaml file that represents a skin in a folder on my hard drive in some random folder. Say D:\Temp2\BlackSkin.xaml.

I've set the permissions correctly to give full access so that's not the issue.

This ResourceDictionary BlackSkin.xaml references a BaseSkin.xaml like so:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="{D:\Temp2\BaseSkin.xaml">
</ResourceDictionary.MergedDictionaries>

So I'm loading this BlackSkin.xaml using XamlReader.Load(...)

It works fine if I put:

Source="D:\Temp2\BaseSkin.xaml"/>

Which is of course a hard-coded path. I would like to provide a relative path, not relative to the current application that is running, but a relative path to the current skin being loaded (BlackSkin.xaml)...aka "D:\Temp2\BaseSkin.xaml"...or...if you will just "BaseSkin.xaml" since they are located in the same folder, which again is not the same folder as the application.

Do I need to define a Custom Markup Extension to do this? And if so, where would I put this in my project? Just make a folder called "Extensions" and create some random .cs file and implement it there? Would such a Custom Markup Extension get called when calling XamlReader.Load(...)?

Then I would need to tie this Custom Markup Extension into the program to get a fully qualified path from some setting or config file in my application and return the path as part of the ProvideValue function?

Are there any other ways of doing this?

Such as where I call XamlReader.Load(fileStream) like so:

public void ApplySkin(string fileName, string baseSkinFileName)
    {
        if (File.Exists(baseSkinFileName))
        {
            ResourceDictionary baseSkinDictionary = null;
            using (FileStream baseSkinStream = new FileStream(baseSkinFileName, FileMode.Open))
            {
                //Read in the BaseSkin ResourceDictionary File so we can merge them manually!
                baseSkinDictionary = (ResourceDictionary)XamlReader.Load(baseSkinStream);
                baseSkinStream.Close();
            }
            using (FileStream fileStream = new FileStream(fileName, FileMode.Open))
            {
                // Read in ResourceDictionary File
                ResourceDictionary skinDictionary = (ResourceDictionary)XamlReader.Load(fileStream);
                skinDictionary.MergedDictionaries.Add(baseSkinDictionary);
                // Clear any previous dictionaries loaded
                Resources.MergedDictionaries.Clear();
                // Add in newly loaded Resource Dictionary
                Resources.MergedDictionaries.Add(skinDictionary);
                fileStream.Close();
            }

And perhaps just remove the BaseSkin.xaml lookup in the BlackSkin.xaml...that is to say remove this block of code from the BaseSkin.xaml?

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="{StaticResource baseSkin}"/> <!--D:\Temp2\BaseSkin.xaml" /-->
</ResourceDictionary.MergedDictionaries>

But then the question becomes what if I have other resources that I want to load in the BlackSkin.xaml, like images or anything else? So I guess the above really only works for a generic single-use situation like this, but what I'm really after is a generic solution that can work for chaining all kinds of different resources into a ResourceDictionary that is an external file located at a random folder on the disk, and resources that may or may-not be co-located with the originating xaml resource Dictionary file.

Which brings me back to the Custom Markup Extensions question.

EDIT:

Well I did try to create a custom markup extension, but it throws a XamlParseException, I think because it doesn't know what the clr-namespace is, again because it's not in any assembly or anything of that nature that can be related back to the running program.

So here is the Xaml that I have now, maybe I got it completely wrong, or maybe I'm going completely down the wrong path...?

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:base="clr-namespace:TimersXP.Extensions">
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="{base:BaseSkinPath}"/> <!--D:\Temp2\BaseSkin.xaml" /-->
</ResourceDictionary.MergedDictionaries>

And this is what I have for the custom extension, just trying to see if I can make it work with a hard-coded path for now in the custom extension. Did I misunderstand something? I'm going off from this example: http://tech.pro/tutorial/883/wpf-tutorial-fun-with-markup-extensions

using System;
using System.Windows.Markup;

namespace TimersXP.Extensions
{
    class BaseSkin : MarkupExtension
    {
        /// <summary>Gets the base skin file path.</summary>
        /// <value>The base skin.</value>
        public string BaseSkinPath
        {
            get { return "D:\\Temp2\\BaseSkin.xaml"; }
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return "D:\\Temp2\\BaseSkin.xaml";
        }
    }
}
Was it helpful?

Solution

I think you can define your own class, inherited from ResourceDictionary, and use it in MergedDictionaries section.

Define:

public class SkinResourceDictionary : ResourceDictionary {
    public static string BaseDirectory { get; set; }

    public new Uri Source {
        get {
            return base.Source;
        }
        set {
            base.Source = BaseDirectory + value;
        }
    }
}

Use:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1">
<ResourceDictionary.MergedDictionaries>
    <local:SkinResourceDictionary Source="\BaseSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
...

But you're still need to decide how to set BaseDirectory property;

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top