26

I have a WPF .net 4.5 application where I am having trouble merging resource dictionaries.

I have the exact same problem as This SO question and This Question but the accepted solution does not work for me.

I have a resource dictionaries declared in my app.xaml as follows (simplified for clarity):

<Application.Resources>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Skin/ResourceLibrary.xaml" />              
            <ResourceDictionary Source="Skin/Brushes/ColorStyles.xaml" />               
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>               
</Application.Resources>

Problem: The app can "SEE" the ColorStyles dictonary when listed in app.xaml, but if I move/nest it inside the ResourceLibrary.xaml, then the ColorStyles.xaml are not "seen" by the app and errors about missing static resources appear.

Here is how I create the ResourceLibrary.xaml dictionary (simplified):

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

    <ResourceDictionary.MergedDictionaries>

        <!--  BRUSHES AND COLORS  -->
        <ResourceDictionary Source="Brushes/ColorStyles.xaml" />

    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Reason for change: My current organization of my resource dictionaries is awful and I need to change it (as I am creating objects more than once). I wanted to have one resource dictionary in a "Skin" folder and then sub-folders for organizing the remaining style dictionaries which would all be merged in the ResourceLibrary.xaml file which in turn would be called in app.xaml.

What I tried: Yes I did try to use the solution from the link above:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Skin/ResourceLibrary.xaml"/>
        </ResourceDictionary.MergedDictionaries>
        <!-- Dummy Style, anything you won't use goes -->
        <Style TargetType="{x:Type Rectangle}" />
    </ResourceDictionary>
</Application.Resources>

but I get the following error on the dummy style line:

Error 2 Property elements cannot be in the middle of an element's content. They must be before or after the content.

Changing the code to the following got rid of the error above, thanks to lisp comment:

<Application.Resources>
    <ResourceDictionary>
        <!--Global View Model Locator-->
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />

        <!-- Dummy Style, anything you won't use goes -->
        <Style TargetType="{x:Type Rectangle}" />

        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Skin/ResourceLibrary.xaml"></ResourceDictionary>             
            <ResourceDictionary Source="Skin/Brushes/ColorStyles.xaml" />
            <ResourceDictionary Source="Skin/NamedStyles/AlertStyles.xaml" />

        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>               
</Application.Resources>

but the Resource Library is still not being called.

I also tried to change all the file paths to pack URI's, but that did not solve the problem either.

I tried moving the resourceLibrary.xaml and the other resource dictionaries into a different class library project (using the same folder structure and files as above). I then used the following URI but I still am not able to access resources declared in the ResourceLibrary.xaml file.

<ResourceDictionary Source="pack://application:,,,/FTC.Style;component/ResourceLibrary.xaml" />

But again, if I add each resource dictionary to the App.Xaml file, using the UIR format above, the resources are usable.

The error is gone, but I am still unable to use resources that are a part of the merged dictionary in the ResourceLibrary.xaml file. I am inclined to agree with the comment of dowhilefor as to whether or not I should use this approach, but I want to figure this out because the most common solution to this problem (see links at top of this post) is not working and maybe this solution could help someone else.

Question: Why is the ResourceLibrary.xaml file being ignored?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
J King
  • 4,108
  • 10
  • 53
  • 103
  • 1
    Is your dummy style related xaml exactly like posted? I have no `Error 2`, you might have a syntax error in your original code, see [this](http://msdn.microsoft.com/en-us/library/bb907325(v=vs.90).aspx) – lisp Jun 12 '13 at 08:14
  • @lisp thanks, I edited my code to show what was actually happening. I had elements declared both above and below the merged dictionaries. So i changed the code but I still have the problem of the ResourceLibrary not being called – J King Jun 12 '13 at 14:34
  • @lisp any of the styles or resources declares in the resourcelibrary.xaml are not loaded and the errors are static resource can't be found or dependency propert.unset is not a valid value for foreground property. Let me know if that is not clear enough and I will post the exact error – J King Jun 12 '13 at 15:08
  • 3
    There is one good tip i can give you: Use as few xamls as possible. Don't try to overorganize it, by splitting it up. We did it with 500 xamls and i'm still waking up at nights crying and sweating. You should have 3 layers max. The app.xaml, which contains ALL resource dictionaries, the Generic.xaml from libs with default controls and of course your few as possible "normal" xamls that are "included" in the app.xaml. And remember, when merging xamls, the order of them is very important. – dowhilefor Jun 12 '13 at 15:10
  • @dowhilefor is the drawback mainly maintenance or is there a performance problem with too many nested xaml dictionaries – J King Jun 12 '13 at 15:21
  • 1
    We did it in the beginning because we wanted higher maintenance and we thought that splitting it into multiple xamls was easier for us to handle. In the end we had an insanely high memory consumption and the performance was horrible. So we had to revert all these xamls that we created over 2 years, and bring it back to where we are now. the biggest problem was having one shared or styles.xaml and "including" it in every other xaml. Don't ever, ever do this. Check one on my old [posts](http://stackoverflow.com/questions/6693320/mergeddictionaries-and-resource-lookup) regarding that problem. – dowhilefor Jun 12 '13 at 15:40
  • @dowhilefor thanks for the heads up. So a simple example, if I have 20 xaml files (5 base control styles, 5 custom control styles, and 10 named styles) I should have them all in the app.xaml merged dictionary. Or combine them all into one xaml? I think that the way I understood resource dictionaries was wrong. So if I put all the styles into one xaml and call that from each of the 50 usercontrols in my app then WPF will create 50 instances of these resources? – J King Jun 12 '13 at 17:25
  • 1
    My Knowledge is still a bit vague, but from my understanding is, no you only create the resources once, but trying to find them is taking longer, because different dictionaries are traversed multiple times. If you have 20 xamls and all merged in the app.xaml in the correct order, everything should be fine. Merging them into one xaml would be even better, but i didn't notice any huge increase after that. – dowhilefor Jun 12 '13 at 17:32
  • What is the BuildAction for your ResourceLibrary.xaml file, it should be `Page`? – S2S2 Jun 13 '13 at 06:08
  • @VS1 it is page, I did try changing it to resource but that did not work either – J King Jun 13 '13 at 15:26

3 Answers3

30

I hava a big problem with MergedDictionaries and I believe that your problem is the same. I want my ResourceDictionaries to be properly organized, which means for me that there are for example seperate Buttons.xaml, TextBoxes.xaml, Colors.xaml and so on. I merge them in Theme.xaml, often all the Styles are in a seperate assembly (so that I could easily switch Themes). My ApplicationResources are as follows:

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="/DefaultTheme;component/Theme.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <Style TargetType="{x:Type Ellipse}"/>
  </ResourceDictionary>
</Application.Resources>

And every StaticResource in the .xamls defined in the main application assembly work, default styles work thanks to the dummy Style. What doesn't work are StaticResources between .xamls inside of Theme. If I define a Style in Buttons.xaml that uses a StaticResource from Colors.xaml, I get an error about StaticResources and UnsetValue. It works if I add Colors.xaml to Application MergedDictionaries.

Solution 0

Abandon organization. Put everything in one .xaml. I believe that is how the ResourceDictionaries were generally supposed to be used because of all the 'problems' with MergedDictionaries (for me this would be a nightmare).

Solution 1

Change all cross-xaml StaticResource references inside of theme to DynamicResource. It works but comes with a price as DynamicResources are 'heavier' than StaticResources.

Solution 2

In every Theme .xaml that uses StaticResources from another .xaml, add this another ResourceDictionary to MergedDictionaries. That means that Buttons.xaml, TextBoxes.xaml and others would have Colors.xaml in their MergedDictionaries. It will result in Colors ResourceDictionary being stored in memory in multiple copies. To avoid that you might want to look into SharedResourceDictionary.

Solution 3

By different ResourceDictionaries setup, different nestings I came up with a theory:

If a StaticResource isn't found above in the same .xaml or in the MergedDictionaries of this ResourceDictionary, it is searched in other top-level MergedDictionaries.

I would prefer to add to ApplicationResources only one .xaml, but I usually end up using two. You dont have to add to ApplicationResources every .xaml that you have in Theme, just - for example - Controls.xaml (with any kind of MergedDictionaries nesting, but no cross-references between Dictionaries of Controls.xaml are allowed) and Common.xaml which contains all common Resources of Controls. In case of Common.xaml nesting is also allowed, but no cross-references, there cannot be seperate Colors.xaml and Brushes.xaml that uses Colors as StaticResources - then you would have to have 3 .xamls added to Application MergedDictionaries.

Now I always use the third solution, but I don't consider it perfect and still would like to know if there is a better way. I hope I correctly interpreted what you described as the same problem as mine.

lisp
  • 4,138
  • 2
  • 26
  • 43
  • great, I think you nailed it, just two quick questions, 1. what kind of library is your external DefaultTheme. A class library or WPF class Library (I am using a class library). 2. How do you build your datatemplates, they often reference styles from another xaml. I can't believe how little documentation there is on these issues, I can't find anywhere else where someone actually explained what is happening, so thank you. Look like I will be going with Option 3 as well. – J King Jun 13 '13 at 15:53
  • 2
    @JKing 1: (I'm using VS2010) When I Check in properties, the output type is "class library", but judging by the ThemeInfo attribute that is present in AssemblyInfo.cs, when I was creating this project I must have chosen some wpf-related option. (When I used to look for better solutions, I tried other things: Generic.xaml is supposed to be special, but it didn't solve my problem, I must have experimented with properties and attributes - to no avail) – lisp Jun 13 '13 at 17:04
  • Last question, do you know why it is discouraged to have a lot of links to resource dictionaries from the application.xaml. Is there a real benefit to merging them into 2 -3 dictionaries and then referencing those from the application.xaml? – J King Jun 13 '13 at 17:07
  • 2
    @JKing 2: I rarely put DataTemplates in my Theme, but your point stands. What you always have to do is split all your resources into tiers. In the first tier there are no StaticResource references to other .xamls, in the second tier there can be only references to first one, and so on. Perhaps you will end up with three or more. I guess I oversimplified when I said I use the third solution, when in fact I use 0, 1 and 3. My first tier has mostly Colors and Brushes and perhaps some very basic Styles to base on, and all this ends up in one .xaml (solution 0). ... – lisp Jun 13 '13 at 17:26
  • 2
    @JKing2 ...When I don't want to mess up my organization or cannot easily split into tiers, I occasionaly switch to DynamicResource (solution 2). I didn't know it was discouraged to have a lot of links in Application.xaml. I don't know any guidelines. Your basic question is here: one file or MergedDictionaries. I guess you get (slightly or dramatically) better performance with putting everything in one file (depends on how many resources you have), but for me seperate files are a must. ... – lisp Jun 13 '13 at 17:41
  • 2
    @JKing ... Then: to merge in App or in Theme? When you Merge everything in App, you avoid most of the problems, but I want my Theme to be reusable (by different Apps), so when I add a new .xaml to Theme I didn't have to update every App. That's why I want as few Dictionaries in App as possible, that's why I don't consider third solution perfect - because I would like to be able to add only one Dictionary in App for everything to work. – lisp Jun 13 '13 at 17:44
  • Then how do you develop your style library project? All the references from control styles to brushes are not resolved. It can be fixed by adding an App.xaml to the project that joins them all, but it's not really logical to add that to a library. – Wouter Nov 11 '13 at 10:37
  • @lisp could you provide a better example of solution 3 or description. I do not understand it. I am using multiple xaml files from a referenced assembly in app.xaml. This works when running, but the designer does not work... – nietras Feb 22 '16 at 12:57
  • Thanks for answer, I came to solution to generate one file based on all resoursedictionaries using tt template – Arsen Mkrtchyan Nov 28 '17 at 09:20
2

I had to introduce themes into our application and faced these exact problems.

The short answer is this: Resource dictionaries can "See" other Resource dictionaries if they come before them in App.xaml. If you try to use MergedDictiories in a file that is not App.xaml, the resource dictionaries will not "See" each other.

For default resources in Generic.xaml: You can use resources defined in App.xaml or a merged dictionary out of App.xaml as DynamicResource only. You can use resources defined in Generic.xaml as StaticResource, but only if your style is defined in Generic.xaml itself and not in a merged dictionary inside of Generic.xaml

For the full answer I have a detailed post in my blog about this issue

My suggested solution: Create whatever XAML hierarchy you want, and place your files in a folder with .txaml extensions. I created a small simple program (provided below in GitHub) that will run as a pre-build event and merge your .txaml files into one long .XAML file.

This allows to structure resources folders and files however you want, without WPF’s limitations. StaticResource and the designer will work always. This is the only solution where you can have CustomControl styles in multiple files, not just one long Generic.xaml.

This will also solve whatever performance issues multiple XAML files create.

Xaml merging program in GitHub

Isak Savo
  • 34,957
  • 11
  • 60
  • 92
Michael_S_
  • 488
  • 4
  • 11
  • 26
0

In addition to @lisp answer, I have written tt template, which take all files from Default.xaml, find them and join into one file, which than we can use in app.xaml

So we can structure files, have performance, static resource will work...

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".xaml" #>

<#
    IDictionary<string, XNamespace> GetNamespaces(XDocument doc)
    {
        return doc.Root.Attributes()
                    .Where(a => a.IsNamespaceDeclaration)
                    .GroupBy(a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, a=>XNamespace.Get(a.Value))
                    .ToDictionary(g => g.Key, g => g.First());
    }

    XDocument GetFlattenResourceDocument(string path)
    {
        var xFilePath = this.Host.ResolvePath(path);
        var doc = XDocument.Load(xFilePath);

        var defaultNs = doc.Root.GetDefaultNamespace();

        var mergedDictElement = doc.Root.Elements(defaultNs + "ResourceDictionary.MergedDictionaries").SingleOrDefault();
        if (mergedDictElement == null)
            return doc;

        var rootNamespaces = GetNamespaces(doc);

        var mergedResourceDictionaries = mergedDictElement.Elements(defaultNs + "ResourceDictionary");
        var addAfterElement = mergedDictElement as XNode;

        foreach(var resourceDict in mergedResourceDictionaries)
        {
            var sourcePath = resourceDict.Attribute("Source").Value;
            var flattenDoc = GetFlattenResourceDocument(sourcePath);

            var flatNamespaces = GetNamespaces(flattenDoc);

            foreach(var key in flatNamespaces.Keys)
            {
                if(!rootNamespaces.ContainsKey(key))
                {
                    var curNamespace = flatNamespaces[key];
                    doc.Root.Add(new XAttribute(XNamespace.Xmlns + key, curNamespace.ToString()));
                    rootNamespaces.Add(key, curNamespace);
                }
            }

            var startComment = new XComment($"Merged from file {sourcePath}");
            var endComment = new XComment($"");

            var list = new List<XNode>();
            list.Add(startComment);
            list.AddRange(flattenDoc.Root.Elements());
            list.Add(endComment);
            addAfterElement.AddAfterSelf(list);

            addAfterElement = endComment;

        }

        mergedDictElement.Remove();

        return doc;
    }
#>
<#= GetFlattenResourceDocument("Default.xaml").ToString() #>
Arsen Mkrtchyan
  • 49,896
  • 32
  • 148
  • 184