Wpf merged resource dictionary no being recognized from app.xaml

前端 未结 3 1767
暗喜
暗喜 2020-12-23 21:49

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 th

相关标签:
3条回答
  • 2020-12-23 22:25

    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

    0 讨论(0)
  • 2020-12-23 22:46

    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() #>
    
    0 讨论(0)
  • 2020-12-23 22:47

    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.

    0 讨论(0)
提交回复
热议问题