Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / WPF

WPF Custom Visualization Intermezzo: Resources

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
24 Aug 2016CPOL14 min read 15.6K   230   10  
(Yet Another) Investigation of WPF resources

Content

Introduction

In the two parts of this series of articles we made extensive use of Resources to define our Styles without actually paying much attention to them, except where it was needed in the discussion at hand. I will (try to) correct this inbalance in this article.

Mind however that I will be talking exclusively about resources defined in ResourceDictionaries.

You can find the other parts of this series here:

  1. WPF Custom Visualization Part 1 of some: Styling
  2. WPF Custom Visualization Part 2 of some: Styling with Triggers
  3. WPF Custom Visualization Intermezzo: Resources (this article)
  4. WPF Custom Visualization Intermezzo 2: Binding
  5. WPF Custom Visualization Part 3 of N: Templating
  6. WPF Custom Visualization Part 4 of N: Addorners

 

What are Resources?

Resources are a means provided by the WPF framework to make something available from a central point. This results in three things:

  1. First, we must define something. This can be any instatiatable class in .NET. Until now (in the previous articles) we have been using Styles exclusively, but we could instiate just about any class defined in .NET.
  2. Second, we need to make it available for use. This means we must somehow be able to refer to it. This is typically done using a ResourceKey.
  3. Finally, we make it available from a central point. In the previous article, this was typically done by defining it in the Resource section of a control in XAML

Basic Resources in WPF: instantiating and referencing objects.

Concepts

A lot of what has been said about how to define Styles, how to reference them, their scope, etc... is valid for Resources in general: after all, the Styles we defined are simply Resources:

As mentioned above, there three components to using Resources:

  1. Defining the object as a resource
  2. Making the object referenceable
  3. Using the resource

 

How to do it?

To define an object as a resource, we must define an instance of it in the Resources section of the scope ini which we want to use it. To make it referencable we must provide a Key and to reference it we use the StaticResource markup extension or the DynamicResource markup extension. (Don't mind this last one too much for now, further I will provide more explanation on the difference bewteen the two.)

A most basic example is the following:

We have a class with definition:

C#
public class MyCustomClass
{
	public string StringProperty { get; set; }
	public int IntegerPoperty { get; set; }

	public override string ToString()
	{
		return "This MyCustomClass instance has a value for StringProperty of [" + StringProperty + "] and a value of IntegerProperty of [" + IntegerPoperty + "]";
	}
}

And we use it as a Resource:

XML
<GroupBox.Resources>
	<!-- define an instance of the class as a resource by defining it in the Resource section
		and make it referenceable by giving it a key -->
	<local:MyCustomClass x:Key="groupBoxLevelInstance" StringProperty="GroupBoxResource" IntegerPoperty="10" />
</GroupBox.Resources>

<!-- use the resource as the content for this button -->
<Button Grid.Row="0" Content="{StaticResource groupBoxLevelInstance}"/>

Above definitions result in following visuals:

Image 1

Don't be fooled however by the fact that this results in a visual representation: that is simply because we reference the resource in the Content property of the button, but a resource does NOT have to result in anything visual.

As Styles had a scope in which they where visible, so do Resources in general. Their scope is hierarchically determined by the container in which they are defined, just as was the case with Styles:

XML
<Grid>
	<StackPanel Grid.Row="1">
		<StackPanel.Resources>
			<local:MyCustomClass x:Key="stackpanelLevelInstance" StringProperty="StackpanelResource" IntegerPoperty="10" />
		</StackPanel.Resources>
		<Button Content="{StaticResource stackpanelLevelInstance}"/>
	</StackPanel>
	<!-- following will throw an exception at runtime:
		the resource is not available in this scope -->
	<!--<Button Content="{StaticResource stackpanelLevelInstance}"/>-->
</Grid>

A Resource is not available in a sibling scope of the element on which it is defined.

In fact, the whole discussion on scoping as in the first article is still valid here:

The scope is hierarchically determined by the container in which the style is defined. This results in following hierarchy:

  1. Application
  2. Window
  3. (Parent) Control
  4. Control

 

This hierarchy is defined by "containment", meaning that a dialog opened by a click on a button inside another window does NOT see the resources defined in this last window

The application contains all windows it displays. A Window with a button opening another Window does NOT contain the opened Window: any resources defining in the opening Window will not be available to the opened Window.

Making Resources available in WPF: the key is the Key

Concepts

The most basic way of making Resources available in an aplication is through the x:Key directive and providing a string. However, Resources made available through this type of key are only referencable in the application itself, after all, how is WPF to know what external library to check? If you want a Resource in a library project to be available in an application you must use the ComponentResourceKey.

There are some other specialized ResourceKey derived classes available like TemplateKey and its derivatives DataTemplateKey and ItemContainerTemplateKey, but these are only valid when adding specific types of objects to the Resources. I will discuss these in a next article.

You may remember from our Style article that we where able to specify a TargetType on our style resource and in doing so didn't need to provide a Key. Turns out this is possible because the Style class defines the DictionaryKeyProperty attribute.

How to do it?

We've seen the basic use using a string in the above samples, so let's fast forward to the ComponentResourceKey:

In a WPF library project we defined two classes:

C#
namespace Resources.ResourceLib
{
	public class DummyClass
	{
	}
}

namespace Resources.ResourceLib
{
	public class OtherDummyClass
	{
	}
}

As you can see, these classes implement absolutely no functionality: they will eventually function as a kind of markers in the ComponentResourceKey

In the same project, we define a Theme/Generic.xaml with following content:

XML
<ResourceDictionary
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:local="clr-namespace:Resources.ResourceLib">
	<!-- to make the style available outside the library we MUST use a ComponentResourceKey -->
	<SolidColorBrush x:Key="{ComponentResourceKey {x:Type local:DummyClass}, ResourceId=myComponentLibBrush}" Color="Red"/>
	<!-- although we use the same name, this actually is another x:Key because we're
		using another type in the ComponentResourceKey -->
	<SolidColorBrush x:Key="{ComponentResourceKey {x:Type local:OtherDummyClass}, ResourceId=myComponentLibBrush}" Color="Green"/>
	<!-- following style is not available outside this dll -->
	<SolidColorBrush x:Key="myLibBrush" Color="Red"/>
</ResourceDictionary>

Notice how we use our two defined classes in the ComponentResourceKey. They enable WPF to find the assembly in which to look for the resources: WPF will look in the assembly in which the classes are defined. Also note how we are using two ComponentResourceKeys with the same ResourceId

To use the above defined resources, we have following XAML:

XML
<GroupBox Header="Resources from external libraries">
	<GroupBox.Resources>
		<SolidColorBrush x:Key="redColor" Color="Red"/>
	</GroupBox.Resources>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
		</Grid.RowDefinitions>
		<Button Grid.Row="0" Content="Backgroundcolor from this application" 
			Background="{StaticResource redColor}"/>
		<Button Grid.Row="1" Content="Backgroundcolor from external library using DummyClass type" 
			Background="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type rlib:DummyClass}, ResourceId=MyComponentLibBrush}}"/>
		<Button Grid.Row="2" Content="Backgroundcolor from external library using OtherDummyClass type" 
			Background="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type rlib:OtherDummyClass}, ResourceId=MyComponentLibBrush}}"/>
		<!-- allthough these compile, they will make your application crash -->
		<Button Grid.Row="3" Content="Backgroundcolor from external library using regular key" 
			Background="{StaticResource myLibBrush}"/>
		<Button Grid.Row="4" Content="Backgroundcolor from external library using regular key" 
			Background="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type rlib:DummyClass}, ResourceId=myLibBrush}}"/>
	</Grid>
</GroupBox>

Above definitions result in following visuals:

Image 2

What is going on here? The first button is a common in-application resource reference and because the resource is defined in the resource section of a parent scope this will succeed. After all, this is what we have been doing until now. Next are two ComponentResourceKeys defined in another library. These also succeed without any problem. Notice that, allthough we use the same ResourceId twice, because we use a different TypeInTargetAssembly WPF handles these as two different keys! The last two will compile but fail on execution with a message similar to following: "Cannot find resource named 'myLibBrush'. Resource names are case sensitive." The first one was to be expected: WPF has no way of determining in what library to search. The second may come a bit as a surprise: after all we are telling WPF where to look by providing a TypeInTargetAssembly. But WPF looks for a key of an identical class, thus you must also define the key in de library as a ComponentResourceKey!

There is one more catch here: in preparing for this article I created an ordinary .NET library project (NOT a WPF control library), put in the classes, defined the Generic.xaml, referenced it from my demo application and ... noticed this didn't work! What was happening here? Will, apparently this Theme/Generic.xaml stuff doesn't "just work": there is no free lunch.

What makes WPF look for this file is following attribute definitions in the AssemblyInfo.cs file:

C#
[assembly:ThemeInfo(
    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
                             //(used if a resource is not found in the page, 
                             // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
                                      //(used if a resource is not found in the page, 
                                      // app, or any theme specific resource dictionaries)
)]

This also means that anything defined outside the Theme/Generic.xaml file is NOT available, even when using the ComponentResourceKey:

Let's say we have a file Dictionary1.xaml in the library project:

XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:local="clr-namespace:Resources.ResourceLib">
	<!-- to make the style available outside the library we MUST use a ComponentResourceKey -->
	<SolidColorBrush x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:DummyClass}, ResourceId=UnreferencableComponentLibBrush}" Color="Red"/>
</ResourceDictionary>

If we use the ComponentResourceKey we will get a similar runtime exception as above:

XML
<GroupBox Header="Resources from external libraries">
	<Button Grid.Row="5" Content="Backgroundcolor from external library using ComponentResourceKey only in the client" Background="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type rlib:DummyClass}, ResourceId=UnreferencableComponentLibBrush}}"/>
</GroupBox>

You may remember from the Styles article that it sometimes wasn't necessary to specify a Key: if you specified a TargetType on the Style, then you didn't need the x:Key. How is this possible? Turns out there is a DictionaryKeyProperty which accomplishes this.

If you use a .NET decompiler and look at the Style class, you'll see following code: (truncated for brevety)

C#
namespace System.Windows
{
  [ContentProperty("Setters")]
  [DictionaryKeyProperty("TargetType")]
  [Localizability(LocalizationCategory.Ignore)]
  public class Style : DispatcherObject, INameScope, IAddChild, ISealable, IHaveResources, IQueryAmbient
  {
  // more code here
  }
}

As you can see, the class definition has an attribute DictionaryKeyProperty which gives the property that can be used as a Key in a dictionary like the resources are. Now, you might look at what other types this attribute is applied to and think you will be able to use the same technique there. Unfortunately, there is more to this then just this attribute. To make a long story short: as this guy on StackOverflow found out, this is only possible for following types:

  • Style
  • DataTemplate

 

Following types have the attribute defined, but do not support this syntax:

  • ControlTemplate

 

Referencing Resources in WPF: your options are...

Concepts

When referencing Resources in XAML you'll mostly see the StaticResource markup extension used. It will suffice in most scenarios, but every now and then it will not work. For example, if you want to reference a Resource not allready known at the time of its use then this kind of referencing will not work. For those cases you can use the DynamicResource markup extension.

How to do it?

We allready saw how to use the StaticResource markup extension in the Basic Resources in WPF section. But what exactly does this mean?

Let's try a few things.

A first case in which the StaticResource does not function and we have to replace it by a DynamicResource reference is in the case of a forward reference. The following example is a bit artificial, but let's say that for some reason the Resource definition follows the referencing of the resource, like in:

XML
<GroupBox Name="DynamicDemos" Header="Dynamic Resource examples">
	<GroupBox.Resources>
		<local:MyCustomClass x:Key="BoundAsStaticResource" StringProperty="BoundAsStaticResource" IntegerPoperty="1" />
		<local:MyCustomClass x:Key="BoundAsDynamicResource" StringProperty="BoundAsDynamicResource" IntegerPoperty="1" />
	</GroupBox.Resources>
	<!-- more XAML here -->
	<GroupBox Grid.Row="5" Header="Late defined resources">
		<Grid>
			<Button Content="{StaticResource LateDefinedResource}" />
			<Button Content="{DynamicResource LateDefinedResource}" />
		</Grid>
		<GroupBox.Resources>
			<local:MyCustomClass x:Key="LateDefinedResource" StringProperty="LateDefinedResource" IntegerPoperty="1" />
		</GroupBox.Resources>
	</GroupBox>
</GroupBox>

If you try to execute this example, it will fail with an exception stating "Provide value on 'System.Windows.StaticResourceExtension' threw an exception" with an InnerException of "Cannot find resource named 'LateDefinedResource'. Resource names are case sensitive.". If you put the Button with the StaticResource reference in comment (like in the sample code) or move the GroupBox.Resources section before this Button it will succeed.

The problem is of course that the Resource is not yet known when the Button is instantiated

Somethinig similar happens when you use a ResourceKey in a custom control, while the ResourceKey is not yet defined:

XML
<UserControl x:Class="Resources.UserControlDynResourceDemo"
			 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
			 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
			 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
			 mc:Ignorable="d" 
			 d:DesignHeight="300" d:DesignWidth="300">
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
		</Grid.RowDefinitions>
		<Button Grid.Row="0" Content="Button with dynamic bound backgroundcolor" Background="{DynamicResource redColor}" />
		<Button Grid.Row="1" Content="Button with static bound backgroundcolor" Background="{StaticResource redColor}" />
	</Grid>
</UserControl>

<GroupBox Name="DynamicDemos" Header="Dynamic Resource examples">
	<GroupBox.Resources>
		<SolidColorBrush x:Key="redColor" Color="Red"/>
	</GroupBox.Resources>
	<!-- more XAML here -->
	<GroupBox Grid.Row="6" Header="Custom control with (then) undefined resourcekey">
		<local:UserControlDynResourceDemo />
	</GroupBox>
</GroupBox>

It will fail with the similar exception "Provide value on 'System.Windows.StaticResourceExtension' threw an exception" with an InnerException of "Cannot find resource named 'redColor'. Resource names are case sensitive.". If you put the Button with the StaticResource reference in comment (like in the sample code) it will succeed.

Another use case for the DynamicResource markup extension is when the Resource itself can change during the lifetime of the application

XML
<GroupBox Name="DynamicDemos" Header="Dynamic Resource examples">
	<GroupBox.Resources>
		<local:MyCustomClass x:Key="BoundAsStaticResource" StringProperty="BoundAsStaticResource" IntegerPoperty="1" />
		<local:MyCustomClass x:Key="BoundAsDynamicResource" StringProperty="BoundAsDynamicResource" IntegerPoperty="1" />
		<SolidColorBrush x:Key="redColor" Color="Red"/>
	</GroupBox.Resources>
	<!-- more XAML here -->
		<Button Grid.Row="0" Content="{StaticResource BoundAsStaticResource}"/>
		<Button Grid.Row="1" Content="Change the instance of the resource" Click="DynDemoButtonChangeInstanceStat_Click" />
		<Button Grid.Row="2" Content="{DynamicResource BoundAsDynamicResource}"/>
		<Button Grid.Row="3" Content="Change the instance of the resource" Click="DynDemoButtonChangeInstanceDyn_Click" />
		<Button Grid.Row="4" Content="Change a property of the resource" Click="DynDemoButtonChangeProperty_Click" />
</GroupBox>			

If we change the Resource which is bound through the StaticResource markup extension, then nothing happens: because we used this markup extension we can not change the value anymore.

C#
private void DynDemoButtonChangeInstanceStat_Click(object sender, RoutedEventArgs e)
{
	DynamicDemos.Resources["BoundAsStaticResource"] = new MyCustomClass() { StringProperty = "BoundAsStaticResourceV2", IntegerPoperty = 1 };
}

If we change the Resource which is bound through the DynamicResource markup extension, then the visuals do change to reflect the new Resource instance:

C#
private void DynDemoButtonChangeInstanceDyn_Click(object sender, RoutedEventArgs e)
{
	DynamicDemos.Resources["BoundAsDynamicResource"] = new MyCustomClass() { StringProperty = "BoundAsDynamicResourceV2", IntegerPoperty = 1 };
}

This effectively acts like the Resource is applied through a Binding. If you are familiar with Bindings, you may wonder what would happen if we change just a single property of our class, which does implement INotifyPropertyChanged:

C#
private void DynDemoButtonChangeProperty_Click(object sender, RoutedEventArgs e)
{
	var currentInstance = DynamicDemos.Resources["BoundAsDynamicResource"] as MyCustomClass;
	currentInstance.StringProperty = "BoundAsDynamicResourceChangePropertyV3";
}

As you can see, nothing is happening: only when you change the full Resource instance does the reference get updated.

All the above definitions will result in following visuals:

Image 3

Re-using Resources in WPF

Concepts

Allthough a ResourceDictionary is an enabler of re-use, you may be willing to re-use the ResourceDictionary itself also. For this we have the MergedDictionaries property of a ResourceDictionary.

By using the MergedDictionaries property we can reference another xaml file defining a ResourceDictionary. I will show you the exact syntax in the following section, but think about this first please: referencing it from somewhere is one thing, but where does the target of the reference reside? Turns out you have several options here:

  • Compiled in the project (either application or library) itself
  • Compiled in another project (which then will typically be a library project)
  • On your harddisk in the application folder or a subfolder of it

How to do it?

The most basic case is when you have a xaml resourcefile compiled in your application, which you want to share on multiple forms:

We have a file "SomeResourceDictionary.xaml" in our project with following content:

XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
					xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<SolidColorBrush x:Key="redInSomeResourceDictionary" >Red</SolidColorBrush>
	<SolidColorBrush x:Key="yellowInSomeResourceDictionary" >Yellow</SolidColorBrush>
</ResourceDictionary>

Mind the words "compiled in" above: this means that the xaml file is embedded into your binaries (typically the application exe or the library dll). Therefore we must also specify the Build Action in the files project properties:

Image 4

If we want to use the resources defined in this file, then we use following:

XML
<GroupBox Header="Mergedresourcedictionary">
	<GroupBox.Resources>
		<ResourceDictionary>
			<ResourceDictionary.MergedDictionaries>
				<ResourceDictionary Source="SomeResourceDictionary.xaml"/>
			</ResourceDictionary.MergedDictionaries>
		</ResourceDictionary>
	</GroupBox.Resources>
	
	<Button Grid.Row="1" Content="Background color comming from merged resource in the GroupBox" Background="{StaticResource yellowInSomeResourceDictionary}" />
		
</GroupBox>

Above definitions result in following visuals:

Image 5

You may not have noticed form the above, but we're actually using a relative URI here. This means the xaml file is being searched for in the same folder as the xaml file referencing it. Let's apply this to a window defined in a subfolder in our project.

Suppose we have following structure in our project:

  • ProjectFolder
  • ControlFolder
    • OtherWindow.xaml
    • SomeResourceDictionary.xaml
  • SomeResourceDictionary.xaml

Let me draw your attention to the fact that we have two equally named files here in different folders. Now, for the content of these files:

XML
<!-- SomeResourceDictionary.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
					xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<SolidColorBrush x:Key="redInSomeResourceDictionary" >Red</SolidColorBrush>
	<SolidColorBrush x:Key="yellowInSomeResourceDictionary" >Yellow</SolidColorBrush>
</ResourceDictionary>
XML
<!-- ControlFolder/SomeResourceDictionary.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
					xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<SolidColorBrush x:Key="redInSomeResourceDictionary" >Blue</SolidColorBrush>
</ResourceDictionary>
<Window x:Class="Resources.ControlFolder.OtherWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="OtherWindow" Height="300" Width="600">
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
		</Grid.RowDefinitions>
		<StackPanel Grid.Row="0">
			<StackPanel.Resources>
				<ResourceDictionary>
					<ResourceDictionary.MergedDictionaries>
						<ResourceDictionary Source="/SomeResourceDictionary.xaml" />
						<ResourceDictionary Source="/ResourceFolder/Dictionary1.xaml" />
					</ResourceDictionary.MergedDictionaries>
				</ResourceDictionary>
			</StackPanel.Resources>
			<Button Content="Button with Backround from entry in Absolute SomeResourceDictionary.xaml" Background="{StaticResource redInSomeResourceDictionary}" />
			<Button Content="Button with Backround from entry in Absolute ResourceFolder/Dictionary1.xaml" Background="{StaticResource magentaInResourceFolderDictionary1}" />
		</StackPanel>
		<StackPanel Grid.Row="1">
			<StackPanel.Resources>
				<ResourceDictionary>
					<ResourceDictionary.MergedDictionaries>
						<ResourceDictionary Source="SomeResourceDictionary.xaml" />
					</ResourceDictionary.MergedDictionaries>
				</ResourceDictionary>
			</StackPanel.Resources>
			<Button Content="Button with Backround from entry in Relative SomeResourceDictionary.xaml" Background="{StaticResource redInSomeResourceDictionary}" />
		</StackPanel>
	</Grid>
</Window>

The above definitions will result in following visuals:

Image 6

There is a caveat here when applying this in a library's Themes/Generic.xaml file. Apparently it is not possible to use these abbreviated relative or absolute URI's in the Generic.xaml of a library project. There we have to use what is called a Pack URI. Which takes us to the following paragraph:

It is also possible to reference XAML files compiled in other assemblies using what is called a Pack URI. A complete description of the Pack URI scheme is beyond the scope of this article, however there are some good resources on the internet.

We have a file "Dictionary1.xaml" in our library project Resources.XamlResourceLib with following content:

XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
					xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<SolidColorBrush x:Key="blueInXamlLibResourceDictionary" >Blue</SolidColorBrush>
	<SolidColorBrush x:Key="yellowInXamlLibResourceDictionary" >Yellow</SolidColorBrush>
</ResourceDictionary>

If we want to use the resources defined in this file, then we use following:

XML
<GroupBox Header="Mergedresourcedictionary">
	<GroupBox.Resources>
		<ResourceDictionary>
			<ResourceDictionary.MergedDictionaries>
				<ResourceDictionary Source="SomeResourceDictionary.xaml"/>
				<ResourceDictionary Source="pack://application:,,,/Resources.XamlResourceLib;component/Dictionary1.xaml"/>
				<ResourceDictionary Source="ExternalDictionary.xaml"/>
			</ResourceDictionary.MergedDictionaries>
			<SolidColorBrush x:Key="redColor" Color="Red"/>
		</ResourceDictionary>
	</GroupBox.Resources>
	<Button Grid.Row="2" Content="Background color comming from merged resource with library source in the GroupBox" Background="{StaticResource blueInXamlLibResourceDictionary}" />
	<Button Grid.Row="3" Content="Background color comming from merged resource with external source in the GroupBox" Background="{StaticResource externalBrush}" />
</GroupBox>

The above definitions will result in following visuals:

Image 7

As you can see the syntax is very similar to what we used for referencing a XAML file in the application itself. The only things which differs is the string used for referencing the external XAML file. And this is thanks to this special Pack URI style string. There is one more thing to notice here: if you look in the AssemblyInfo.cs file in the project you will notice I commented out the ThemeInfo code and the referencing still works!

C#
// We do NOT need this when directly referencing the XAML file
//[assembly:ThemeInfo(
//    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//                             //(used if a resource is not found in the page,
//                             // or application resource dictionaries)
//    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//                                      //(used if a resource is not found in the page,
//                                      // app, or any theme specific resource dictionaries)
//)]

This makes the biggest difference with the ComponentResourceKey which also allows referencing resources in library projects: if you use the MergedDictionaries in combination with a Pack URI, you reference the XAML file, where when using the ComponentResourceKey you reference the resource itself. Notice how with a ComponentResourceKey we didn't have to include anything: simply using the ComponentResourceKey was sufficient to access the resource in the library project. Notice also how with using ComponentResourceKey we had to define the resource in the Theme/Generic.xaml file. Anything defined outside this file is not accessible as demonstrated above.

Now, suppose you want to be able to change some resources, like colors, without the need to recompile your application. Because until now the Build Action of our ResourceDictionary xaml files was set to Page, the file got compiled into our binaries. But if we change the Build Action to Content, then the xaml file will not get compiled into our binaries but we must copy it ourselfs next to the binary or set the "Copy to Output Directory" property of the file to one of the following:

  • Copy always
  • Copy if newer

Image 8

How to apply this in practice you can see in the above example with the ExternalDictionary.xaml file and the second button.

But all this merging comes with a new problem: what happens if we merge dictionaries containing equally named (or key-ed if you like) resources? Let's find out.

We have the following XAML resourcefiles:

XML
<!-- EqNamedKeyDictionary1.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
					xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<SolidColorBrush x:Key="commonKeyDictionary" >Red</SolidColorBrush>
	<SolidColorBrush x:Key="uniqueKeyIn1Dictionary" >Yellow</SolidColorBrush>
</ResourceDictionary>
XML
<!-- EqNamedKeyDictionary2.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
					xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<SolidColorBrush x:Key="commonKeyDictionary" >Blue</SolidColorBrush>
	<SolidColorBrush x:Key="uniqueKeyIn2Dictionary" >Green</SolidColorBrush>
</ResourceDictionary>

As you can see, we have in each file an equally named entry: x:Key="commonKeyDictionary"

First, what happens if we merge two dicionaries into a third dictionary, and the first two have overlapping Keys?

XML
<GroupBox Header="Mergedresourcedictionary: Equally named keys and Ordering: Case 1">
<!-- Multiple merges with same key -->
	<GroupBox.Resources>
		<ResourceDictionary>
			<ResourceDictionary.MergedDictionaries>
				<ResourceDictionary Source="EqNamedKeyDictionary1.xaml"/>
				<ResourceDictionary Source="EqNamedKeyDictionary2.xaml"/>
			</ResourceDictionary.MergedDictionaries>
		</ResourceDictionary>
	</GroupBox.Resources>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="auto" />
			<RowDefinition Height="auto" />
			<RowDefinition Height="auto" />
		</Grid.RowDefinitions>
		<Button Grid.Row="0" Content="Button with Background from equally named keys" Background="{StaticResource commonKeyDictionary}" />
		<Button Grid.Row="1" Content="Button with Background from unique key 1" Background="{StaticResource uniqueKeyIn1Dictionary}" />
		<Button Grid.Row="2" Content="Button with Background from unique key 2" Background="{StaticResource uniqueKeyIn2Dictionary}" />
	</Grid>
</GroupBox>

The above definitions will result in following visuals:

Image 9

Next, what happens if we merge a dicionary into another dictionary, and they have overlapping Keys?

If we merge first and then define the new resource into the dictionary, following happens:

XML
<GroupBox Header="Mergedresourcedictionary: Equally named keys and Ordering: Case 2">
	<!-- Multiple merges with same key -->
	<GroupBox.Resources>
		<ResourceDictionary>
			<ResourceDictionary.MergedDictionaries>
				<ResourceDictionary Source="EqNamedKeyDictionary1.xaml"/>
			</ResourceDictionary.MergedDictionaries>
			<SolidColorBrush x:Key="commonKeyDictionary" Color="Blue"/>
		</ResourceDictionary>
	</GroupBox.Resources>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="auto" />
			<RowDefinition Height="auto" />
		</Grid.RowDefinitions>
		<Button Grid.Row="0" Content="Button with Background from equally named keys" Background="{StaticResource commonKeyDictionary}" />
		<Button Grid.Row="1" Content="Button with Background from unique key 1" Background="{StaticResource uniqueKeyIn1Dictionary}" />
	</Grid>
</GroupBox>

The above definitions will result in following visuals:

Image 10

But if we first define the resource in the dictionary and then merge another dictionary, following happens:

XML
<GroupBox Header="Mergedresourcedictionary: Equally named keys and Ordering: Case 3">
	<!-- Multiple merges with same key -->
	<GroupBox.Resources>
		<ResourceDictionary>
			<SolidColorBrush x:Key="commonKeyDictionary" Color="Blue"/>
			<ResourceDictionary.MergedDictionaries>
				<ResourceDictionary Source="EqNamedKeyDictionary1.xaml"/>
			</ResourceDictionary.MergedDictionaries>
		</ResourceDictionary>
	</GroupBox.Resources>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="auto" />
			<RowDefinition Height="auto" />
		</Grid.RowDefinitions>
		<Button Grid.Row="0" Content="Button with Background from equally named keys" Background="{StaticResource commonKeyDictionary}" />
		<Button Grid.Row="1" Content="Button with Background from unique key 1" Background="{StaticResource uniqueKeyIn1Dictionary}" />
	</Grid>
</GroupBox>

The above definitions will result in following visuals:

Image 11

This is exactly the same as above: so it looks like no mather in what order you define the merging, it is always applied first en thus overridden by any following equally named/key-ed values!

Conclusion

That's it for resources. But this isn't everything: most of the above should be seen in the light of Styles. Next will be a second intermezzo on Bindings.

Version history

  • Version 1.0: Initial version
  • Version 1.1: Following changes:
    • Added reference to other articles the series

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --