Question

I'm creating an application in WPF (.Net 3.5) that I want to be able to customize on a per-client basis. I abstracted out the resources to external xaml (theme) files. With the xaml file built as a Page, this works perfect.

Now I want to use XamlReader to dynamically load the theme's xaml files. This way I can compile the application and others can customize the app (per client) without having to deal with re-compiling or having access to the source code. Here's a snapshot of what I'm doing:

Directory.GetFiles(pathToThemeDirectory).Each((file) =>
{
     using (Stream stream = new FileStream(file, FileMode.Open))
     {
          var dir = Directory.GetCurrentDirectory();
          var dict = XamlReader.Load(stream) as ResourceDictionary;
          Application.Current.Resources.MergedDictionaries.Add(dict);
     }
});

Now that seems to parse the Xaml correctly. However, I now receive an error that looks like this:

Cannot convert string '/Path/To/Image/File.ico' in attribute 'Icon' to object of 
type 'System.Windows.Media.ImageSource'. Cannot locate resource
'/Path/To/Image/File.ico'.  Error at object 'System.Windows.Setter'

I want to be able to change these resources per client (logo images, etc), so I have changed the build action from Resource to None and told it to Always copy to the output directory.

Are there any suggestions on how to parse Xaml at runtime and change my configuration to get image resources that have the need to not be compiled in the assembly? Or am I completely off base and there is a way better way to achieve this theme customization?


Update:

Here's the exact line that is giving the error:

<Style x:Key="MainWindow">
    <Setter Property="Window.Icon" Value="/Assets/icon.ico" />
</Style>

But that is because this is the first asset loaded. Here's another example:

<ImageBrush x:Key="NavigationBackgroundImage" ImageSource="Assets/bg.png" TileMode="Tile" Stretch="UniformToFill"/>

These are just used in styles accessed via:

{StaticResource MainWindow}

or

{StaticResource NavigationBackgroundImage}
Was it helpful?

Solution

It seems like you need to create a ParserContext according to this blog.

In that example he was doing:

var resourceDictionary = XamlReader.Load(fileStream) as ResourceDictionary;
if(resourceDictionary != null)
{
    Resources.MergedDictionaries.Add(resourceDictionary)
}

But afterwards the code was changed to:

var applicationDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if(!String.IsNullOrEmpty(applicationDirectory ))
{

    var runtimeResourcesDirectory = Path.Combine(applicationDirectory , "RuntimeResources");
    var pc = new ParserContext
    {
        BaseUri = new Uri(runtimeResourcesDirectory , UriKind.Absolute)
    };
    if(Directory.Exists(runtimeResourcesDirectory ))
    {
        foreach (string resourceDictionaryFile in Directory.GetFiles(runtimeResourcesDirectory , "*.xaml"))
        {
            using (Stream s = File.Open(resourceDictionaryFile, FileMode.Open, FileAccess.Read))
            {
                try
                {
                    var resourceDictionary = XamlReader.Load(s, pc) as ResourceDictionary;
                    if (resourceDictionary != null)
                    {
                        Resources.MergedDictionaries.Add(resourceDictionary);
                    }
                }
                catch
                {
                    MessageBox.Show("Invalid xaml: " + resourceDictionaryFile);
                }
            }
        }
    }
}

Hope this helps!

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