How to make a WPF style inheritable to derived classes? - c#

In our WPF app we have a global style with TargetType={x:Type ContextMenu}. I have created a MyContextMenu that derives from ContextMenu, but now the default style does not apply.
How can I tell WPF that I want MyContextMenu to inherit the default style from ContextMenu? Hopefully I can do this from within my control itself (via static ctor metadata override or something?) and not have to mess around in any xaml.

If you have a Style defined in your application like so:
<Style TargetType="{x:Type ContextMenu}" ...
Then that is an implicit Style, not a default Style. Default Styles are generally located in the same assembly as the control or in matching assemblies (i.e. MyAssembly.Aero.dll).
Implicit Styles are not automatically applied to derived types, which is probably what you are seeing.
You can either define a second Style, like so:
<Style x:Key="{x:Type ContextMenu}" TargetType="{x:Type ContextMenu}" ...
<Style TargetType="{x:Type local:MyContextMenu}" BasedOn="{StaticResource {x:Type ContextMenu}}" ...
Or you can leverage the Style property of your control. You could do the following from XAML
<local:MyContextMenu Style="{DynamicResource {x:Type ContextMenu}}" ...
or you can do this in your MyContextMenu like so:
public MyContextMenu() {
this.SetResourceReference(StyleProperty, typeof(ContextMenu));
}

In complement to CodeNaked's excellent suggestions, I tried specifying Style in the XAML part of MyContextMenu:
<ContextMenu x:Class=LocalProject.MyContextMenu"
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"
xmlns:local="clr-namespace:AdelSoft_WS_FRA_Test.Composants"
mc:Ignorable="d"
Style="{DynamicResource {x:Type ContextMenu}}">
The compiler warned me that it cannot resolve the resource, but at runtime it looks quite able to.
Naturally, you can also use
Style="{StaticResource ContextMenuStyleName}">
if you use style names.

Related

WPF ContextMenu and MenuItem Cannot set OverridesDefaultStyle property in the default Style WPF

I'm trying to create library with components that have an extended appearance configuration.
Instead of creating more styles and templates, I can inherit style from default style for custom component, and override few values.
I created CustomizableContextMenu and CustomizableMenuItem classes inherited from default ContextMenu and MenuItem and extended them by additional constructor. For example for CustomizableContextMenu.
static CustomizableContextMenu()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomizableContextMenu),
new FrameworkPropertyMetadata(typeof(CustomizableContextMenu)));
}
Styles and templates are in separate files that are referenced in the file "Themes/Generic.xaml".
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/FluentControls;component/Themes/CustomizableContextMenuStyle.xaml"/>
<ResourceDictionary Source="pack://application:,,,/FluentControls;component/Themes/CustomizableMenuItemStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style BasedOn="{StaticResource CustomizableContextMenuStyle}" TargetType="{x:Type components:CustomizableContextMenu}"/>
<Style BasedOn="{StaticResource CustomizableMenuItemStyle}" TargetType="{x:Type components:CustomizableMenuItem}"/>
Until I try to make style and template in the same app (without overriding DefaultStyleKeyProperty) and making reference to style in MainWindow.xaml everything works fine. But now (except other components such Button, TextBox, ComboBox, CheckBox, etc.) I have a problem with it.
I'm trying to create CustomizableButton (the same problem appears when I'm using standard Button) with CustomizableContextMenu and CustomizableMenuItem, but it didn't work. The message I've got from the IDE was "Cannot set OverridesDefaultStyle property in the default Style"
This is implementation code of CustomizableContextMenu.
<Components:CustomizableButton
...
Content="I have ContextMenu!">
<Button.ContextMenu>
<Components:CustomizableContextMenu
Background="#C0000000"
BorderBrush="#C00078D7"
BorderThickness="1"
Foreground="White">
<Components:CustomizableMenuItem
Background="#C0000000"
BorderBrush="#C00078D7"
Foreground="White"
Header="Item Test 1"
InputGestureText="Ctrl+1"
Margin="0,1"/>
</Components:CustomizableContextMenu>
</Button.ContextMenu>
</Components:CustomizableButton>
It's possible to do it in easy way, or not? And I must remove override of DefaultStyleKeyProperty, and import style in the traditional way to MainWindow.xaml and use it by setting property in component definition?
Remove the following <Setter> from the default style:
<Setter Property="OverridesDefaultStyle" Value="True" />
Since the style in themes/generic.xaml is the default style by defintion, it cannot override another default style.

ItemContainerStyle cleared by loading custom style

So, I have this issue with making a custom treeview with custom treeviewitem:s where the ItemContainerStyle gets cleared by loading the style from the custom style.
It work like this. I have custom MyTreeViewItem based on TreeViewItem.
<TreeViewItem x:Class="UI.MyTreeViewItem"
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="450" d:DesignWidth="800">
<TreeViewItem.Resources>
<Style x:Key="MyTreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="Background" Value="#AEFFC1" />
</Style>
</TreeViewItem.Resources>
</TreeViewItem>
As you can see I have just have a simple coloring here just to make sure that the styling it self works. This how ever wont load unless I do like this in code behind.
EDIT: I know things like coloring don't need to be put here as there was intended to be a template here instead. How ever since noting really worked, I just stripped this down to the bones to make sure I put something super simple in that I know should work in case it was becaouse of the template it self.
public partial class MyTreeViewItem : TreeViewItem
{
public MyTreeViewItem()
{
InitializeComponent();
this.Loaded += MyTreeViewItem_Loaded;
}
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
this.Style = Resources["MyTreeViewItemStyle"] as Style;
}
}
This works grate. Have used this several other times with other controls to have custom styling for controls needed to be loaded up with out having to bother to "restyle" everything over and over again.
How ever there are a issue I come across with this. And that is when ItemContainerStyle is being used of this kind of custom styled controller.
<local:BaseTreeView x:Class="My.Navigator.NavigatorTreeView"
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"
xmlns:ui="clr-namespace:UI;assembly=BaseCode"
d:DesignHeight="450" d:DesignWidth="800">
<ui:MyTreeView ItemsSource="{Binding Path=Nodes}">
...
<ui:MyTreeView.ItemContainerStyle>
<Style TargetType="{x:Type ui:MyTreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="Selected" Handler="TreeView_SelectedItemChanged" />
<EventSetter Event="Expanded" Handler="TreeView_NodeExpanded" />
<EventSetter Event="Collapsed" Handler="TreeView_NodeCollapsed" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</ui:MyTreeView.ItemContainerStyle>
</ui:MyTreeView>
</local:BaseTreeView>
This ui:MyTreeView.ItemContainerStyle as you see above gets totally ignored once the style is loaded, by this.Style = Resources["MyTreeViewItemStyle"] as Style; in the MyTreeViewItem_Loaded.
That means these Setters, EventSetters and Triggers will not fire at all, as they still are needed to be able to be added as as additional rules.
How can this be solved, so that the predefined styling in the custom control can be loaded and by using this control, you can still can hook up unique rules, like above with out having the the predefined overrule them?
It's not really clear why you are doing what you are doing. All I can say is that you are overwriting the Style value by assigning it explicitly after the control was initialized with the value from the TreeView.ItemContainerStyle.
Normally, on a UserControl, you would set the properties locally on the element:
<TreeViewItem x:Class="UI.MyTreeViewItem"
...
d:DesignHeight="450" d:DesignWidth="800"
Background="#AEFFC1">
</TreeViewItem>
or in code-behind:
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
this.Background =
new SolidColorBrush(ColorConverter.ConvertFromString("#AEFFC1"));
}
When writing a custom Control, you would provide a default Style in Generic.xaml. This is the best solution as it allows the control to be styled (where custom styles are allowed to override default values provided by the default Style). External styles are merged implicitly. You should prefer a custom Control over a UserControl.
Your current code does not allow styling as you forcefully override values provided by a custom Style:
// Overwrite previous property value.
this.Style = someValue;
This is programming 101, first grade: an assignment always overwrites the old value (reference) of the variable.
Assumming that you are knowing what you are doing and you don't want to use one of the above solution, you must manually merge both styles using the Style.BasedOn property:
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
var defaultStyle = Resources["MyTreeViewItemStyle"] as Style;
defaultStyle.BasedOn = this.ItemContainerStyle;
this.Style = defaultStyle;
}
See: Control authoring overview: Models for Control Authoring

WPF: Which properties are applied to the ControlTemplate

I'd like to understand which properties of an xaml Control are applied to the ControlTemplate of that Control. F.e. If I create a Control based on the Window Class like this:
(This is very simplified — It doesn't make sense in the current state I know...)
public class BaseWindow : Window {
public BaseWindow() { }
}
And the Template:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:Microsoft.Windows.Shell;assembly=Microsoft.Windows.Shell"
xmlns:local="clr-namespace:Arctic">
<Style TargetType="{x:Type local:BaseWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BaseWindow}">
<Grid Background="Transparent">
<Border Background="{TemplateBinding Background}"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Now, when I specify a BaseWindow Control in my app the Margin Property is applied to the BaseWindow without specifying a TemplateBinding. The Background isn't, I have to declare the TemplateBinding in the Template in order to achieve that. Can you explain to me why some properties are applied to the ControlTemplate by default and others are not?
My guess is, that the Window.xaml (Default Window Template of WPF) binds to some properties like the Margin but ignores some like Background. If that is true, then I do not understand why I can set the Background in a Window Control and it is applied to it. Seems like the Window binds to some properties and stops doing that when you derive from it…
This is probably completely wrong — I just wanted to explain my thoughts.
Window class inherit FrameworkElement and all its properties including FrameworkElement.Margin. Same goes for Control.Background. Your question is why you have to do something to have Control.Background working.
Answer is simple:
Margin is used in layouting, its functionality is implemented/provided by FrameworkElement and it happens always, invisible for you and disregarding of ControlTemplate (because all framework elements participate in layouting and use margin).
Background, in turn, is provided to be use by visuals. It's up to you how to use it, because only you know how control will looks like. Control doesn't know what to do with that property.
So, you have to use TemplateBinding to bind Background to some color in your ControlTemplate, but Margin works without need to do anything in control template.

Apply template to control and children

I've been tinkering around with WPF MDI, which sets the control template for MDI Child objects. So when you add an MdiChild object and set its Content to a UserControl it looks good, but if you inherit from MdiChild then it doesn't work.
The template code looks something like this:
<Style TargetType="{x:Type local:MdiChild}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MdiChild}">
<!-- ... -->
I'd like this template to apply not just to MdiChild, but also to anything that derives from it. How can I go about this? The only way I can think of is to create a style targeting each derived class that is based on the MdiChild style, but that's not very desirable.
You have to declare style for each derived type but with WPF you have power to inherit from base style using BasedOn.
<Style TargetType="{x:Type local:DerivedMdiChild}"
BasedOn="{StaticResource {x:Type local:MdiChild}}">
......
</Style>
This way all setters, triggers etc. will be inherited and you don't have to redefine them again for each derived style. Moreover, it gives you power to override setter of base style in case you want some different behaviour in derived version.

Setting DataContext on DataGrid.RowStyle

Using the following sample R# (resharper) is not able to find the datacontext of the Row style and warns about a wrong binding (at runtime works fine). Seems like the Style is not getting the DataContext of the ItemsSource:
MainWindow.xaml:
<Window x:Class="TestDatacontext.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:testDatacontext="clr-namespace:TestDatacontext"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance testDatacontext:MainWindowVM}" >
<DataGrid ItemsSource="{Binding Items}" >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Window>
MainWindowVM:
using System.Collections.ObjectModel;
namespace TestDatacontext
{
class MainWindowVM
{
public ObservableCollection<ItemVM> Items { get; private set; }
}
}
ItemVM:
namespace TestDatacontext
{
class ItemVM
{
public string Name { get; set; }
}
}
You are correct, ReSharper has no knowledge about how RowStyle will be used in this particular control (is it style per every item of ItemsSource? or some kind of header style and bindings will have access to ItemsSource object itself?), so it stops traversing tree looking for DataContext type on Style declaration.
This issue can be solved with additional annotation on Style declaration:
<Style TargetType="DataGridRow" d:DataContext="{d:DesignInstance vms:ItemVM}">
<Setter Property="Header" Value="{Binding Name}" />
</Style>
Project will compile fine, VS designer and R# will work, but VS xaml support will produce 1 error in errors window - "Property 'DataContext' is not attachable to elements of type 'Style'". That's a bit annoying, but works. Other way is to quilify property type like this:
<Style TargetType="DataGridRow">
<Setter Property="Header" Value="{Binding (vms:ItemVM.Name)}" />
</Style>
But it produces VS xaml support error too :) and have slightly different behavior in runtime - this binding will work only with Name property of ItemVM type and will not work if somehow VM object will be replaced with some other object of different type with Name property at runtime (so binding became "strongly-typed").
We are still looking for a better way to solve this kind of problems in ReSharper 8.0 and make VS designer happy, sorry for confusing!

Categories