Using BasedOn Style with static ResourceDictionary in different Assembly - wpf

Background
I have multiple projects in my solution and in one project I've made a static class containing a merged ResourceDictionary, which I use in all my other projects:
namespace GUI.Shared.Resources
{
public static class ResourceFactory
{
public static ResourceDictionary _resources = null;
public static ResourceDictionary Resources
{
get
{
if (_resources == null)
{
_resources = new ResourceDictionary();
_resources.MergedDictionaries.Add(new ResourceDictionary() { Source = new Uri("pack://application:,,,/GUI.Shared;component/Resources/VectorIcons.xaml") });
_resources.MergedDictionaries.Add(new ResourceDictionary() { Source = new Uri("pack://application:,,,/GUI.Shared;component/Resources/ButtonStyles.xaml") });
}
return _resources;
}
}
}
}
With my styles defined in the xaml files ButtonStyles.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="SpecialButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Red"/>
</Style>
</ResourceDictionary>
Normally I use the style like this:
<UserControl x:Class="GUI.Modules.TextControl.Views.TextControlView"
x:Name="TextControl"
xmlns:resources="clr-namespace:GUI.Shared.Resources;assembly=GUI.Shared">
<Grid>
<Button Style="{Binding Path=[SpecialButtonStyle], Source={x:Static resources:ResourceFactory.Resources}}">
</Grid>
</UserControl>
Problem
Now I want to extend my style locally in a View. I found out that you do this by using the BasedOn property.
<Button>
<Button.Style>
<Style TargetType="Button" BasedOn="{Binding Path=[SpecialButtonStyle], Source={x:Static resources:ResourceFactory.Resources}}">
</Style>
</Button.Style>
</Button>
Only my binding method is not allowed, giving the error:
A Binding cannot be set on the BasedOn property of type Style. A Binding can only be set on a DependencyProperty of a DependencyObject.
How can I use this style from my ResourceFactory.Resources as a BasedOn style?

The BasedOn property is a regular CLR property, not a dependency property, which means you cannot bind it. Furthermore the indexer syntax among other binding path constructs are only available in the Binding markup extension, not in x:Static, StaticResource or DynamicResource.
You can create your own markup extension based on the x:Static implementation, but that is very complex and the wrong thing to do. Instead, you should think about following a different built-in approach to your issue.
If you want to share resources, why create a static ResourceFactory at all? WPF already supports resource provision with the App.xaml resource dictionary and scoped resources within each FrameworkElement through the Resources property. Create a resource dictionary for your shared resources e.g. like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/GUI.Shared;component/Resources/VectorIcons.xaml"/>
<ResourceDictionary Source="pack://application:,,,/GUI.Shared;component/Resources/ButtonStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
As you can see, this is the same as your ResourceFactory, but easier to use. Include this dictionary into the application resources or any other resources section of any project using a pack URI e.g.:
<UserControl x:Class="GUI.Modules.TextControl.Views.TextControlView"
x:Name="TextControl"
xmlns:resources="clr-namespace:GUI.Shared.Resources;assembly=GUI.Shared">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/GUI.Shared;component/Resources/SharedResourceDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button>
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource SpecialButtonStyle}">
<!-- ...your style definition. -->
</Style>
</Button.Style>
</Button>
</Grid>
</UserControl>
There is no need for Binding here and it is much easier to access resources this way. You can access the resources in child controls, too, because access to resources is scoped to where they are defined.

Related

XAML designer with UserControls in DLL doesn't resolve resource

I have a solution in Visual Studio 2019, made of some projects built as DLL using WPF .NET 5.
Now I need to create some UserControls inside one of this projects, that I will load in a future WPF .NET 5 application, but the problem is that the XAML designer is not able to preview the UserControl with StaticResource that are in an external ResourceDictionary.
I simplify the code:
<UserControl.Resources>
<Style x:Key="sButton" TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource color_UC_BorderBrush}"/>
</Style>
</UserControl.Resources>
Having only DLL I don't have an Application with shared ResourceDictionary and color_UC_BorderBrush could not be resolved for preview.
In the following way instead the XAML designer works properly, but I don't want that every single UserControl loads his own ResourceDictionary everytime:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MyLib;component/ResourceDictionaries/myRD.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="sButton" TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource color_UC_BorderBrush}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
In my future WPF .NET 5 application I will use shared ResourceDictionary but for now how can I manage to let the XAML designer works properly inside a DLL or how can I make the MergedDictionary defined in the XAML ignorable?
Maybe there are other ways I don't have in mind. Thank you.
You can't omit merging the resource into a ResourceDictionary within the scope of the control that depends on it.
But instead of merging the same resource into each control's ResourceDictionary, you can choose a ResourceDictionary of a common parent. The XAML resource lookup strategy will traverse the logical tree to search for the required resource, starting at the local ResourceDictionary of the control and ending at the root, before checking App.xaml and Generic.xaml.
In case of the UserControl, you would have to use the DynamicResource markup extension. This is, because the content will only be part of of the logical tree during runtime. In design time, the root from the user control's content point of view is the containing UserControl.
You should know that DynamicResource has a performance penalty. So you typically would try to avoid it.
Also defining the resource in each control's ResourceDictionary will introduce a performance penalty.
The best solution is to define the resource in the application's root control like Window.Resources or even better in the ResourceDictionary inside App.xaml.
Using App.xaml to host the merged ResourceDictionary would also allow to use the cheaper StaticResource from inside the UserControl to lookup the merged resources.
Please read Static and dynamic resources to learn more about the lookup behavior of the XAML engine.
The following example chooses the MainWindow as the host for the merged resources. It is the root of all it's contained elements and therefore reachable from any location in its logical tree.
MainWindow.xaml
Make the resource available for any element that is a child of MainWindow:
<Window>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- The resource dictionary that contains the shared 'color_UC_BorderBrush' resource -->
<ResourceDictionary Source="pack://application:,,,/MyLib;component/ResourceDictionaries/myRD.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<MyUserControl />
</Window>
MyUserControl.xaml
<UserControl>
<UserControl.Resources>
<Style x:Key="sButton" TargetType="Button">
<Setter Property="BorderBrush"
Value="{DynamicResource color_UC_BorderBrush}" />
</Style>
</UserControl.Resources>
<Button Style="{StaticResource sButton}" />
</UserControl>

How to create custom controls using generic.xaml in Silverlight 5 without using WPF?

I want to create a custom controls datagrid using the generic.xaml.I have created the custom controls using the User control template in Silverlight 5. I am not using WPF.
How is it possible to create using generic.xaml?
What you describe is called a Templated Control in Silverlight.
You have to provide three things:
a class containing your control's logic
a default Style with a ControlTemplate
an entry in generic.xaml that includes the default Style
Example: MyFeeblefezer.cs
public class MyFeeblefezer : Control
{
public MyFeeblefezer() { DefaultStyleKey = typeof(MyFeeblefezer); }
}
and MyFeeblefezer.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="MyFeeblefezer">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="MyFeeblefezer">
<Grid>
<!-- here goes your visible control UI parts -->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
and themes/generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyFeebleProject;Component/MyFeeblefezer.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Defining WPF Window style in class library

Currently I am developing some WPF class library, which will have several WPF Windows and trying to create my own window ControlTemplate for these Windows to make more nice design of these windows (inspired by this article: Reusing Control Templates in Resource Dictionaries).
Problem is, then it's a WPF class library not an application assembly, where i can use app.xaml and to define my resource dictionary reference & etc...
Using code below i getting an error: StaticResource reference 'MyWindowStyle' was not found
<Window x:Class="SomeERP.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Style="{StaticResource MyWindowStyle}">
<Window.Resources>
<!-- My Window Style -->
<Style x:Key="MyWindowStyle" TargetType="Window">
<Setter Property="Background" Value="Transparent" />
<Setter Property="WindowStyle" Value="None" />
<Setter Property="AllowsTransparency" Value="True" />
<Setter Property="Opacity" Value="0.95" />
<Setter Property="Template" Value="{StaticResource MyWindowTemplate}" />
</Style>
<!-- Window Template -->
<ControlTemplate x:Key="MyWindowTemplate" TargetType="{x:Type Window}">
<Grid>
</Grid>
</ControlTemplate>
</Window.Resources>
</Window>
I suspect i get this error because in my case it's not predeclared before Window declaration as in application's case in app.xaml which i don't have in class libary. I am pretty new in WPF and just starting to use WPF design possibilities.
If you need the style only once, the solution is pretty simple: Just define the style in-place
<Window.Style>
<!-- My Window Style -->
<Style TargetType="Window">
...
</Style>
</Window.Style>
However, if you need the style in more than one window, it is reasonable to define the style in a resource dictionary. Then you can integrate the resource dictionary in the window's resources and set the style accordingly:
<Window.Resources>
<!-- My Window Style -->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
<!-- path to the resource dictionary -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resource>
<Window.Style>
<StaticResource ResourceKey="MyWindowStyle"/>
</Window.Style>
Looks like i've found solution to my problem in this post: Assembly-wide / root-level styles in WPF class library and according to this post What's the difference between StaticResource and DynamicResource in WPF?
A StaticResource will be resolved and assigned to the property during the loading of the XAML which occurs before the application is actually run. It will only be assigned once and any changes to resource dictionary ignored.
A DynamicResource 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. A good example would be a forward reference to a resource defined later on in the XAML. Another example is a resource that will not even exist until runtime. It will update the target if the source resource dictionary is changed.
That was what i needed, as i have class library, i don't have app.xaml and can't predeclare resources for Window. I just need to use DynamicResource rather than StaticResource to get it working.
Thanks for attention :)
Just add the external assembly:
<Application.Resources>
<ResourceDictionary>
Source="/MyAssemblyName;component/MyResources.xaml"
</ResourceDictionary>
</Application.Resources>

DynamicResource Cannot be resolved

I have been trying to learn about resources and styles, I want to create a chromeless window.
I have an example that acheives what I want via the following simple extracts of xaml.
I have a Resource set in Themes/Generic.xaml
<Style x:Key="BorderlessWindowStyle" TargetType="{x:Type Window}">
<Setter Property="AllowsTransparency" Value="true" />
<Setter Property="WindowStyle" Value="None" />
<Setter Property="ResizeMode" Value="CanResizeWithGrip" />
<Setter Property="Background" Value="Transparent" />
</Style>
I have a main window:
<Window x:Class="Project1.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Project1"
x:Name="Window"
Title="Shell" Height="576" Width="1024" Style="{DynamicResource BorderlessWindowStyle}">
<Grid></Grid>
But the style is not being applied and VS designer states it cannot resolve the resource.
The example I have been looking at does things this way and I cannot discover the difference between what I have seen done and what I am trying to do.
I thought that Genric.xaml was a 'special' resource dictionary that should be discoverable by my Window control - and I am guessing this assumption is my error.
What do I need to do to make this work? (Now I understand I can set these properties in the Window xaml directly, and I have done so and get the effect I want. BUT I really want to undersatnd using the Generic.xaml resource dictionary way as I have presented here)
best regards
John.
Themes/generic.xaml file is automatically used to find default styles for Custom Controls. In your case you have an ordinary Window with custom style. You cannot define this style in Window.Resources section, because the style should be defined at a higher level. The only higher level of Window is App.xaml, because the Window is in fact its child. That's why the solution for your question is to place the style into App.Resources section.
Thought I would add the following example in case it helps some others out. To add a resource dictionary to your app.xaml file you can add the following xaml code to the app.xaml file.
<Application x:Class="ProjectX.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ProjectX"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Use the Black skin by default -->
<ResourceDictionary Source="Resources\ResourceFile.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Where 'Resources' coudl be a folder in your project that contains the Resource Dictionary file (ResourceFile.xaml).
The you can add code to your resource dictionary like such:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ProjectX.Resources">
<!-- The Background Brush is used as the background for the Main Window -->
<SolidColorBrush x:Key="MainBackgroundBrush" Color="#FF202020" />
</ResourceDictionary>
And then finally, dynamically bind to your resource dictionary doing something like:
<Window
x:Class="ProjectX.MainWindow"
Title="Family.Show" Height="728" Width="960"
Background="{DynamicResource MainBackgroundBrush}"
ResizeMode="CanResizeWithGrip">
</Window>

DataTemplates in resource dictionaries don't inherit from app.xaml styles?

I added custom named styles to the app.xaml.
I created an external resource dictionary (which I attach in the merged dictionaries of the app.xaml) and when I try to use one of the above named styles in the rcource dictionary, it says that there is no such style.
Also the default styles (i.e. unnamed styles that apply for the entire application) don't apply on the template elements.
Note: The Build Action of the templates is 'Page'.
Here is an example of how my code is written:
<Application x:Class="Application"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShutdownMode="OnExplicitShutdown">
<Application.Resources>
<ResourceDictionary>
<Style
x:Key="StackPanelStyle"
TargetType="StackPanel"
BasedOn="{StaticResource {x:Type StackPanel}}">
<Setter Property="Margin" Value="5"/>
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="Height" Value="40"/>
</Style>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Templates/DataTemplate1.xaml"/>
<ResourceDictionary Source="/Templates/DataTemplate2.xaml"/>
<ResourceDictionary Source="/Templates/DataTemplate3.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
This an example of the data template:
<DataTemplate DataType="{x:Type Entity}" x:Key="NameDataTemplate">
<Expander>
<StackPanel>
<--The following line produces: StackPanelStyle was not found.-->
<StackPanel Style="{StaticResource StackPanelStyle}">
<Label Content="Name:"/>
<TextBox Text="{Binding Name}"/>
</StackPanel>
</StackPanel>
</Expander>
</DataTemplate>
Any ideas?
Do I have to merge the dictionaries in a different way?
The code won't work well because the DataTemplate in the resource dictionary doesn't know which one is using it, it just been used. Like Hollywood mode. They compiled separately.
To make this work, you can put your style which in app.xaml in the same resource dictionary of the DataTemplate or if you don't like this coupling, you can put it in a different resource dictionary, and merge it into the DataTemplate's resource dictionary.
The Build Action for your App.xaml should be ApplicationDefinition, and the build action for your Resource Dictionary files should be Page. I'm guessing you have both of those correct because they are default (and I see that you mentioned about Page already).
I can't think of any other problem with your situation. As long as your static resources are defined in the correct order, which they appear to be, it should be able to find them when you run your application.
Edit
Debugging idea: Create a fresh resource dictionary called "TestDictionary.xaml" with a simple button style. Make sure this dictionary is in the same folder as your other dictionaries (DataTemplate1.xaml, etc.). Put a link to only TestDictionary in MergedDictionaries (comment out the others). Put a button on your startup window and apply the style. See if just that works. If it fails, you know you have an issue with your merge. If it succeeds, something about your DataTemplate might be the problem.

Resources