How to Define Common Resources Across WPF Applications (including templates)? - wpf

I am looking for a way to define a WPF resource (for now, to be used as a static resource) that is accessible from everywhere within my application.
My resource is defined in this resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="flatButtonStyle" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" TargetType="{x:Type Button}">
<Setter Property="BorderThickness" Value="4"/>
</Style>
</ResourceDictionary>
The selected answer to this question indicates that I must merge that resource dictionary into my App.xaml file (of a project called AppWideResources):
<Application x:Class="AppWideResources.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/AppWideResources;component/CommonResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
At first, this seems to work; the button in this window is appropriately styled in a flat way with an extra-thick border:
<Window x:Class="AppWideResources.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AppWideResources" Height="300" Width="300"
>
<StackPanel>
<Button Style="{StaticResource flatButtonStyle}" Content="Test" HorizontalAlignment="Stretch"/>
</StackPanel>
</Window>
However, this stops working as soon as I use my shared resource in a control template:
My (extremely simplified, for the purpose of this question) control:
using System;
using System.Windows;
using System.Windows.Controls;
namespace AppWideResources
{
public class MyControl : Control
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
}
}
... and the corresponding Themes\Generic.xaml file:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AppWideResources">
<Style TargetType="local:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyControl">
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource flatButtonStyle}" Content="1"/>
<Button Style="{StaticResource flatButtonStyle}" Content="2"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
This control is then inserted into my window:
<Window x:Class="AppWideResources.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AppWideResources"
Title="AppWideResources" Height="300" Width="300"
>
<StackPanel>
<Button Style="{StaticResource flatButtonStyle}" Content="Test" HorizontalAlignment="Stretch"/>
<local:MyControl/>
</StackPanel>
</Window>
If I run this, a XamlParseException is thrown, saying that the static resource flatButtonStyle cannot be found.
The only workaround I have found is by explicitly merging the common resource dictionary into the local resource dictionary of the control template:
<ControlTemplate TargetType="local:MyControl">
<ControlTemplate.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/AppWideResources;component/CommonResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ControlTemplate.Resources>
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource flatButtonStyle}" Content="1"/>
<Button Style="{StaticResource flatButtonStyle}" Content="2"/>
</StackPanel>
</ControlTemplate>
However, not only is this quite verbose and error-prone with several lines of redundant code (my actual project contains quite a few templated controls, not just one), I am also concerned about wasting system resources by loading the static resources several times (once for every time CommonResources.xaml is included). (Is that concern justified? Or does WPF use the same instances every time when loading a particular resource dictionary?)
So, the question is: What is the proper way to make a WPF resource accessible throughout the whole application, including control templates?

StaticResource will be resolved and assigned to the property during the loading of the XAML which occurs before the application is actually run. So, static resource has to be available at the time of loading.
It works for normal button since resource look up continues till application resources and resolve it from there. But MyControl template still not loaded into app visual tree so it can't traverse upto application resources.
There are two ways to achieve that:
Merge ResourceDictionary in Generic.xaml as well which you have already achieved.
Other way is to use DynamicResource which assigns an Expression object to the property during loading but does not actually lookup the resource until runtime when the Expression object is asked for the value. This defers looking up the resource until it is needed at runtime.
Read it here - StaticResource v/s DynamicResource.
This will work without merging dictionaries in Generic.xaml:
<Button Style="{DynamicResource flatButtonStyle}" Content="1"/>
<Button Style="{DynamicResource flatButtonStyle}" Content="2"/>

Related

Only last icon in XAML shown when icon used from resources

when I try to use a material design icon from the icon pack that is defined in the ResourceDictionary, only the first icon in XAML is rendered at run time. I've followed an example that can be found here.
Example follows:
App.xaml:
<Application x:Class="TestWpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpf"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/TestWpf;component/Dictionary1.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Dictionary1.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<materialDesign:PackIcon x:Key="CashIcon" Kind="Cash" />
</ResourceDictionary>
MainWindow.xaml:
<Window x:Class="TestWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Button Content="{DynamicResource CashIcon}" />
<Button Content="{DynamicResource CashIcon}" />
<Button Content="{DynamicResource CashIcon}" />
</StackPanel>
</Window>
And the result is a window that looks like this:
In xaml editor all buttons have icons on them, as expected:
Why is this happening and, more important, how to fix this?
P.S. We've found out that if you create two Cash icons in the ResourceDictionary and apply each to a button, then they will both be shown but again, only once, you can't have say 3 buttons and 2 icons in ResourceDictionary.
one more solution is to use a non-shared resource (x:Shared Microsoft docs)
<materialDesign:PackIcon x:Key="CashIcon" Kind="Cash" x:Shared="False"/>
x:Shared Attribute: When set to false, modifies WPF resource-retrieval behavior so that requests for the attributed resource create a new instance for each request instead of sharing the same instance for all requests.
A scenario for x:Shared="false" is if you define a FrameworkElement or FrameworkContentElement derived class as a resource and then you introduce the element resource into a content model. x:Shared="false" enables an element resource to be introduced multiple times in the same collection (such as a UIElementCollection). Without x:Shared="false" this is invalid because the collection enforces uniqueness of its contents. However, the x:Shared="false" behavior creates another identical instance of the resource instead of returning the same instance.
The PackIcon type is a Control. An element in the visual tree in WPF can only have a single parent. In other words, the pack icon is still a single instance added as child of the first button, then moved to the second, then to the third. You will in fact have to create multiple instances of the pack icon.
Instead of creating resources, you could use the PackIcon markup extension.
<StackPanel>
<Button Content="{materialDesign:PackIcon Cash}"/>
<Button Content="{materialDesign:PackIcon Cash}"/>
<Button Content="{materialDesign:PackIcon Cash}"/>
</StackPanel>
Depending on your actual scenario, you could alternatively create a DataTemplate, which will automatically create instances of the pack icons for each button.
<DataTemplate x:Key="CashPackIconTemplate">
<materialDesign:PackIcon Kind="Cash" />
</DataTemplate>
<StackPanel>
<Button ContentTemplate="{StaticResource CashPackIconTemplate}"/>
<Button ContentTemplate="{StaticResource CashPackIconTemplate}"/>
<Button ContentTemplate="{StaticResource CashPackIconTemplate}"/>
</StackPanel>

Access values from xaml file in the code(View) directly

I have a file (icons.xaml) that I added as a file in the Resources of my project. The file content is given below:
Icons.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<VisualBrush x:Key="Bucket"
Stretch="Uniform">
<VisualBrush.Visual>
<Viewbox Stretch="xxxx">
<Canvas Width="xxxxx"
Height="xxxx"> and so on...
Icons.xaml file's properties in vs 2017
Now, I want to consume the VisualBrush mentioned above, in my XAML (View) code directly. The lines are given below:
XAML Code (View)
<Setter Property="Icon">
<Setter.Value>
<Rectangle Fill="{Binding Bucket, Source={x:Static resx:Resources.Icons}}"/>
</Setter.Value>
</Setter>
The "resx" namespace is defined as:
xmlns:resx="clr-namespace:MyProjectNamespace.Properties"
When I run my project I cannot see my icons being binded to the rectangle's fill property and I am getting some xaml intellisense error which is "Cannot resolve property "Bucket" in the context of type "string".
Objective: I want to use the visualbrush key directly in the XAML in my Rectangle's Fill property in my View code.
Note: I have a restriction that I cannot use the code in the view which is given below, I cannot use <ResourceDictionary> tab in the xaml code.
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyProjectNamespace;component/Resources/Icons.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Am I following the right approach regarding my objective or should I change this approach?
I have seen the methods that are used here in this answer but none of them are working Get values from *.resx files in XAML
I think the main issue here is that Binding is actually looking for a property called Binding on the Resources.Icons object.
Try this:
<Setter Property="Icon">
<Setter.Value>
<Rectangle Fill="{Binding Bucket, Source={x:Static resx:Icons}}"/>
</Setter.Value>
</Setter>
Binding is not necessary, just try:
<Rectangle Fill="{x:Static resx:Resources.Icons.Bucket}"/>

How to use style defined in another xaml file inside a style

I had MultiSelComboBoxStyle defined in a xaml resource file, and in another xaml resource file, I have
<Style x:Key="DataGridDemoStyle" TargetType="{x:Type DataGrid}">
....
<ComboBox x:Name="ccBox" Grid.Row="0" Grid.Column="1" Width="9" Height="18" VerticalAlignment="Top" Margin="0" Style="{StaticResource MultiSelComboBoxStyle}" Panel.ZIndex="1"/>
....
</Style>
I put every xaml in App.xaml and the compiler did find this MultiSelComboBoxStyle style , but debugging the program says MultiSelComboBoxStyle was not found. In App.xaml
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DataGridCustomizations.xaml" />
<ResourceDictionary Source="MultiSelComboBoxStyle.xaml"></ResourceDictionary>
It turns out I need to do
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MultiSelComboBoxStyle.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
If needs to use styles from different resource files.

Resolving resources from one UserControl to a parent UserControl

I've got a user control UserControl1, which defines a style in its resources. That user control contains an instance of UserControl2, which references that style:
<UserControl x:Class="UserControl1">
<UserControl.Resources>
<Style x:Key="MyStyle" />
</UserControl.Resources>
<Grid>
<UserControl2 />
</Grid>
</UserControl>
<UserControl x:Class="UserControl2">
<Grid Style="{StaticResource MyStyle}">
</Grid>
</UserControl>
However, UserControl2 cannot find that style resource, even though it is in the logical tree (within the resources of UserControl1). How can I get UserControl2 to find the resources in UserControl1?
You can do it but, but I would suggest using a ResourceDictionary instead.
Anyway, if you want to do it this way you can use FindAncestor to find the parent and access the Resource you want out of the Parent ResourceDictionary
<UserControl x:Class="UserControl1">
<UserControl.Resources>
<Style x:Key="MyStyle" />
</UserControl.Resources>
<Grid>
<UserControl2 />
</Grid>
</UserControl>
<UserControl x:Class="UserControl2">
<Grid Style="{Binding Resources[MyStyle], RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}}">
</Grid>
</UserControl>
Beacause Resource is a dictionary you can access using the key just like in code behind
I had the same issue and was able to solve it by referencing the resource through DynamicResource instead of StaticResource:
<UserControl x:Class="UserControl2">
<Grid Style="{DynamicResource MyStyle}">
</Grid>
</UserControl>
The compiler does still give a warning that the resource cannot be resolved.

How to set tooltip for user control (from style file)

I created my control which looks like that
<UserControl BorderBrush="#A9C2DE" HorizontalAlignment="Left" x:Class="WPFDiagramDesignerControl.Components.UcWBSBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="86" Width="151" >
<UserControl.Resources>
<ResourceDictionary Source="Tooltip.xaml"/>
</UserControl.Resources>
<Grid x:Name="MainGrid">
<TextBox Name="txtBox" Style="{StaticResource DefaultStyle}" >
</TextBox>
</Grid>
I also have a file with style for tooltip "Tooltip.xaml"
How can I use this style for entire UserControl?
Usually I did this with that code
<TextBox ToolTip="{StaticResource tooltipname}"/>
But it was easy because file with style was in resource dictionary of control where I placed textbox. However I can't do sth like that
<UserControl BorderBrush="#A9C2DE" HorizontalAlignment="Left" ToolTip="{StaticResource tooltipname"}/>
Because at this point my style isn't in resource dicionary yet.
I was trying to use this syntax
<UserControl.ToolTip> </UserControl.ToolTip>
but I don't konow how should I refer to static resource
Maybe it is lame question but I just don't konow how to do it :)
One option is to just use DynamicResource instead of StaticResource to defer the lookup until runtime and then use the attribute syntax:
<UserControl ... ToolTip="{DynamicResource tooltipname}" ...
You can also write the StaticResourceExtension using element syntax so that you can write it after the Resources section:
<UserControl.Resources>
<ResourceDictionary Source="Tooltip.xaml"/>
</UserControl.Resources>
<UserControl.ToolTip>
<StaticResourceExtension ResourceKey="tooltipname"/>
</UserControl.ToolTip>

Resources