Accessing resources defined in Controls Library - wpf

So I have got a few DataTemplates in a User Control Library. These DataTemplates reside in themes\generic.xaml resource dictionary and can be consumed in the library project without problem.
This library project is used by the main desktop application project, but at runtime Application.Current.FindResource() cannot locate any of the resources defined in the library. (I thought it would!)
So I went ahead and merged that resource dictionary into Application resource dictionary:
<Application x:Class="Application">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/ControlsLibraryProj;component/themes/generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
and voila! it can now access those DataTemplates. My question is, did I do it correctly? Do I need to merge a dictionary defined in a referenced project? Somehow I have the impression that Application.Current.FindResource() would have access to all the resources defined in all loaded modules (exe and dll). Is that correct and have I unnecessarily loaded them twice by merging?

My question is, did I do it correctly?
Yes.
Do I need to merge a dictionary defined in a referenced project?
Yes, if you intend to actually use the styles defined in the referenced Project in your app project.
The only exception is the default control styles, i.e. the default styles for any controls that are defined in the referenced project. These are the ones that you typically define in themes/generic.xaml and these will be applied to any instances of the corresponding Controls that you create in your application without you having to merge themes/generic.xaml.

If you wrote the user control library by your own, I would rather suggest you using generic.xaml for declaring resources just for your custom controls. If you do not want to or you cannot do it, IMHO you should use ComponentResourceKey.
You can find a sample here (take a look to the "Defining resources at the theme level" section).
So your resource will be declared as:
<LinearGradientBrush
x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, ResourceId=ButtonBrush}"
StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="White" Offset=".8" />
</LinearGradientBrush>
And you can retrieve it
ComponentResourceKey brushKey = new ComponentResourceKey(typeof(Painter), "MyEllipseBrush");
ellipseBrush = (Brush)Application.Current.TryFindResource(brushKey);
Please pay attention because:
Implicit style application does not occur on the theme level. Suppose
you want all the labels on your controls to have a certain style. If
you define the style in at the element level, you do not have to give
the style an explicit key, the labels will use the style
automatically. This is not the case for resources at the theme level.
You must define a key and reference the style every place you want to
use it.
I hope this can help you.

Related

Include one ResourceDictionary in another

I'm designing some themes for my C# / XAML application and would like to define my theme in one XAML file (ResourceDictionary), with variations in colour in several other XAML files (ResourceDictionaries).
So, I'm attempting:
<Style TargetType="ListBox">
<Setter Property="Foreground" Value="{StaticResource ResourceKey=ForegroundBrush}" />
<Setter Property="Background" Value="{StaticResource ResourceKey=BackgroundBrush}" />
</Style>
In a generic theme XAML file which is in a class library and is not referenced from my main app. I then have a second file:
<Color x:Key="BackgroundColour" A="#FF" R="#10" G="#10" B="#40" />
<Color x:Key="BackgroundColour2" A="#FF" R="#10" G="#10" B="#FF" />
<Color x:Key="BorderColour" A="#FF" R="#00" G="#00" B="#FF" />
<Color x:Key="ForegroundColour" A="#FF" R="#FF" G="#FF" B="#FF" />
<Color x:Key="ForegroundColour2" A="#FF" R="#80" G="#80" B="#FF" />
<LinearGradientBrush x:Key="GradientForegroundBrush" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="{StaticResource ForegroundColour}" Offset="0"/>
<GradientStop Color="{StaticResource ForegroundColour2}" Offset="1"/>
</LinearGradientBrush>
I can obviously then have several of these files defining different colours on the same basic theme. I have attempted:
<ResourceDictionary Source="basestyle.xaml" x:Key="basestyle" />
I would then reference bluestyle.xaml, redstyle.xaml etc... from my application. This works well if I copy the entire theme across to each file, but being able to reuse the basic code seems neater.
Is there a way to do this?
If you want to separate the resources like brushes or specific styles from your theme styles and control templates, you can simply create the generic Themes\Generic.xaml resource dictionary with references to recource keys as you did in your example with e.g. ForegroundBrush.
Then you can create a specific style resource dictionary, e.g. redstyle.xaml that contains all the resources with the correspondig keys. Alternatively, you could additionally create another basestyle.xaml resource dicitonary that contains some resources shared by all conrete theme variations that you do not want to store in the theme resource dictionary either.
In order to apply a style, add the necessary resource dictionaries to the App.xaml file as merged dictionaries. Please note, that the order matters. Include resource dictionaries that are needed by other resource dictionaries first. The lookup of StaticResources and DynamicResources will find the resource with a matching key, that was added last here. For more information on that, refer to Lookup behavior for XAML resource references.
<Application x:Class="YourApplication"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Base style that includes all default resources and styles. -->
<ResourceDictionary Source="basestyle.xaml"/>
<!-- Red style that includes just specific resources and style that changed. -->
<ResourceDictionary Source="redstyle.xaml"/>
<!-- Your control theme that references the resources and styles. -->
<ResourceDictionary Source="Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
If you intend to switch the styles at runtime, you will have to replace the corresponding dictionaries in code, e.g.:
In App.xaml.cs by modifying the Resources dictionaries
By using Application.Current.Resources.
If you do not replace all dictionaries, including your generic control themes dictionary, you will have to use the DynamicResource markup extension instead of StaticResource, because the latter one does not pick up changes to references resources at runtime.
The StaticResource Markup Extension processes a key by looking up the value for that key in all available resource dictionaries. Processing happens during load, which is when the loading process needs to assign the property value. The DynamicResource Markup Extension instead processes a key by creating an expression, and that expression remains unevaluated until the app runs, at which time the expression is evaluated and provides a value.
Yes you can create different Resource.xaml files in same project, you just need to load that files in App.cs code as,
if(_theme=="theme1")//you can use switch statement if themes are more
{
ResourceDictionary myResourceDictionary1 = new ResourceDictionary();
myResourceDictionary1.Source = new Uri("theme1.xaml", UriKind.Relative);
Application.Current.Resources.MergedDictionaries.Add(myResourceDictionary1);
}
else
{
ResourceDictionary myResourceDictionary2 = new ResourceDictionary();
myResourceDictionary2.Source = new Uri("theme2.xaml", UriKind.Relative);
Application.Current.Resources.MergedDictionaries.Add(myResourceDictionary2);
}

Override Control Style

I have created a CustomControlLibrary.dll, that contains a Control (MyControl), with its style in the Generic.xaml of the Themes folder, as per the default automatic setup.
If I want to include that dll in 2 different projects, each of which providing a custom style for "MyControl" to give it a different look, where do I put those custom styles?
I thought I just had to have a Themes\Generic.xaml for each application, that defines the custom style, but having done this, it still ends up using the style defined in CustomControlLibrary.dll
You have CustomControlLibrary.dll which is the owner of MyControl. Inside that dll you have your themes defined. Any other project that may contain your dll doesnt haven to have themes defined.
Other projects may define their custom style for MyControl in their window resource for example.
Styles may be defined at any level. :)
Check this link out:
http://msdn.microsoft.com/en-us/library/ms745683.aspx
Solution will be to define specific style for each project and apply it correspondingly.
But keep in mind that you can create proper style "inheritance" by using BasedOn property. After all you should get a set of styles that you'll manage in MergedDictionaries:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/CustomControlLibrary;component/styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>

Resources not resolving from ResourceDictionaries

I'm having problems resolving Resources from my ResourceDictionaries.
I decided to refactor my rather large ResourceDictionary into individual dictionary files, organized into subfolders.
I have a ResourceLibrary.xaml under Resources:
<ResourceDictionary x:Class="MyProject.Resources.ResourceLibrary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- Colours -->
<ResourceDictionary Source="Colors/ConnectedCellColor.xaml" />
<!-- Brushes -->
<ResourceDictionary Source="Brushes/ConnectorCellBrush.xaml" />
<!-- Control Templates -->
<ResourceDictionary Source="ControlTemplates/ConnectorCellTemplate.xaml" />
<!-- Base Styles -->
<ResourceDictionary Source="BaseStyles/ConnectorBaseStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
The class is there for a reason, in the code behind, I can add [Export(typeof (ResourceDictionary))] so MEF can find it.
I have a View: (simplified)
<UserControl x:Class="MyProject.ConnectorCellView"
Style="{StaticResource ConnectorBaseStyle}"
</UserControl>
ConnectorBaseStyle:
<ResourceDictionary>
<Style x:Key="ConnectorBaseStyle" TargetType="UserControl">
<Setter Property="Template" Value="{StaticResource ConnectorCellTemplate}" />
</Style>
</ResourceDictionary>
The template has StaticResources to try and get a Brush and a colour.
All of these StaticResources will not resolve any more.
I thought it might have been an order issue, but since these resources are contained in a plugin to my main program, I use MEF and ImportMany to get all the exported ResourceDictionaries, and in my Caliburn.Micro bootstrapper:
public void OnImportsSatisfied()
{
foreach (ResourceDictionary resourceDictionary in ResourceDictionaries)
{
Application.Resources.MergedDictionaries.Add(resourceDictionary);
}
}
(Neat trick I found somewhere)
I can actually run my program, and when that view is created it throws an exception when trying to set the style:
System.InvalidCastException
Unable to cast object of type 'MS.Internal.NamedObject' to type System.Windows.FrameworkTemplate'.
The only information I've found relating to this has to do with the order resources are defined, but from the order I have them in ResourceLibrary, it should work.
When the exception is thrown, I can examine Application.Current.Resources.MergedDictionaries,
and see the resources.
I've tried various ways of specifying the Source in ResourceLibrary
<ResourceDictionary Source="/MyProject;component/Resources/BaseStyles/ConnectorBaseStyle.xaml" />
etc, no effect on finding them. These resources are only used by the plugin code.
The only thing that seemed to work was changing all the StaticResources to DynamicResources
which doesn't make sense to me, if it is an order issue, why would Static work when they're all in the same file?
Some of my styles used BasedOn, and they don't work with DynamicResource.
Can you help me understand why this is happening, and how to make it work?
It is an ordering problem but not with the ordering of your merging - it's with the order of loading. Here's basically what happens when the ResourceLibrary dictionary loads:
ConnectedCellColor instantiates
ConnectedCellColor loads into ResourceLibrary
ConnectorCellBrush instantiates
ConnectorCellBrush loads into ResourceLibrary
ConnectorCellTemplate instantiates
ConnectorCellTemplate loads into ResourceLibrary
ConnectorBaseStyle instantiates
ConnectorBaseStyle loads into ResourceLibrary
The problem here is that where before with your single file you had a single instantiation step, you now have that broken up into multiple steps, each of which happen independently. When ConnectorBaseStyle is instantiated ConnectorCellTemplate has been loaded but ResourceLibrary's contents aren't known to ConnectorBaseStyle at this point. With DynamicResource this isn't a problem because those references can just resolve at step 8, but StaticResource requires immediate resolution at step 7.
The simplest fix is to use Dynamic wherever you can. For places that require Static (like BasedOn) you need to guarantee that the resource will be available during instantiation, either by also merging, for example, ConnectorCellTemplate into ConnectorBaseStyle, or by merging everything that's needed into App.xaml which is available to everything. This can make things complicated and hard to manage as you get more files and do merging into multiple places but at least the resource system is smart enough to recognize duplicates so in the case above you would still only get a single instance of ConnectorCellTemplate even though it is being merged at two places.

Design templates in a class library at top scope

I use a class library (WPF user control library) to host some user controls which other (C#-) applications in the project solution consume. I want these controls to use XAML ControlTemplates residing at the top scope of the class library. The ControlTemplates do not have to be consumed outside the class library.
Here a template declaration:
<ControlTemplate TargetType="{x:Type Button}" x:Key="TemplateImageButtonSmall">
<Grid>
<Image Name="img" Source="/PSCommonUI;component/Images/Buttons/ButtonMinus_normal.png"/>
</Grid>
</ControlTemplate>
Then I have a user control in the class library, containing:
<Button Height="57" Margin="10,0,6,5" Name="button3" Template="{StaticResource TemplateImageButtonSmall}" Width="82">
In an application, I can use the App.xaml file for defining the templates. However, in a class library I don't have this option.
I have searched the web and found some answers including the use of a generic.xaml file, ComponentResourceKey, merging resource files and other stuff I find exaggeratedly complicated.
Also I read that theme definitions (resources in general) shouldn't reside in a class library.
But if I need some themes ONLY in this class library for the there hosted controls, how is best practice then?
Thanks in advance,
Julian
I am not sure what you meant, however, if you want child UIElements from a specific UIElement and below to use control templates, then you can define the templates in a resource dictionary and merge the dictionary into the top control that you want the dictionary to be visible to.
Edit:
The assembly just contains the classes and resources within it. It has no events of its own (e.g. OnApplicationLoaded).
A control's XAML can contain resources of its own (e.g. control templates) for consumption by itself and child controls and thus define default styling.
Your application can merge the resource dictionaries into any level of the tree (application, window, control, ...) and thus override defaults.
If you want the styling to be dynamic (overrable by importing resource dictionaries) then using the DynamicResource keyword your XAML. If your resource is defined in the same XAML and can not be overridden then use the StaticResource keyword.
Add a resource dictionary to your class library and define your resources (templates) there. It doesn't have to be generic.xaml.
Then in each user control or other .xaml file reference the resource dictionaries you require using Xaml similar to:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="... path to dictionary 1"/>
<ResourceDictionary Source="... path to dictionary 2"/>
<ResourceDictionary Source="... etc"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
You can then use resource keys from the merged dictionaries.

Can I merge a resource XAML dictionary in a single page's resources?

My application has a single page with an explicit dark background (dark image), but the rest of the application uses the system colors. Is there a way to use the merged dictionary technique outlined here, but only for a single page, in order to not have to explicitly set the colors (and styles for TextBox controls, etc) on each control one by one?
Thanks.
The technique you have linked to is not limited to the App.xaml. It can be used in any definition of a ResourceDictionary. Everywhere you see a Resources property an implicit ResourceDictionary is created for you when it is accessed. However in all these places you can also explicitly define one. This will allow you to also manipulate its MergedDictionaries property.
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<ResourceDictionary>
<ResourceDicitonary.MergedDictionaries>
<ResourceDictionary Source="urlToAnotherXamlFile" />
</ResourceDicitonary.MergedDictionaries>
</ResourceDictionary>
</Grid.Resources>
.... Content ....
</Grid>

Resources