Objective
I have a custom control targeting a ToggleButton
that will also work with a Button
. So I want to use a common default ControlTemplate
for both types.
The strategy I tried was to set TargetType="{x:Type ButtonBase}"
in the template and this works fine if it is explicitly set on a Button
or ToggleButton
.
Implicit
Custom Control Library
In a resource dictionary called Generic.xaml in the Themes folder on the project root...
<Style TargetType="{x:Type ButtonBase}">
<Setter Property="Template">
<Setter.Value>
<!--Modified Control Template-->
<ControlTemplate TargetType="{x:Type ButtonBase}">
In the control's class I set the metadata for the user control type in it's static constructor using FrameworkElement.DefaultStyleKey...
static ContentToggle()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ContentToggle),
new FrameworkPropertyMetadata(typeof(ButtonBase)));
}
Consuming WPF Application Project
App.xaml...
<Application x:Class="Spec.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<!--Get a reference to the window to establish View Context-->
<RelativeSource x:Key="View" Mode="FindAncestor"
AncestorType="{x:Type Window}" />
<ResourceDictionary.MergedDictionaries>
<!--Local Style-->
<ResourceDictionary Source="pack://application:,,,/ButtonStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xaml...
<Window x:Class="Spec.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:ContentToggleButton;assembly=ContentToggleButton"
Title="MainWindow" Height="350" Width="525">
<Grid>
<b:ContentToggle Name="Toggle" Height="30"
Content="{Binding options, RelativeSource={StaticResource View}}"
/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public List<string> options { get; set; }
public bool initialState { get; set; }
public MainWindow ()
{
options = new List<string> { "Checked", "UnChecked" };
initialState = false;
InitializeComponent();
}
}
There is also a file called ButtonStyle.xaml that defines brushes to be used by the custom control. It is exposed on the root of the app by the merged dictionary in App.xaml.
Result
The template of the ContentToggle instance
is null and there is no visual for the styled control (when I snoop the control it has no child elements).
My understanding is that the automatic ButtonBase
style/template will be used for my control. What am I missing?
Explicit
The custom control works as expected if the style/template is explicitly declared on the control. The following works with the style target set to ButtonBase
...
Consuming WPF Application Project
In App.xaml...
<Application x:Class="Spec.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<!--Get a reference to the window to establish View Context-->
<RelativeSource x:Key="View" Mode="FindAncestor"
AncestorType="{x:Type Window}" />
<ResourceDictionary.MergedDictionaries>
<!--custom control-->
<ResourceDictionary Source="pack://application:,,,/ContentToggleButton;component/Themes/Generic.xaml" />
<!--Local Style-->
<ResourceDictionary Source="pack://application:,,,/ButtonStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
In MainWindow.xaml...
<Window x:Class="Spec.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:ContentToggleButton;assembly=ContentToggleButton"
Title="MainWindow" Height="350" Width="525">
<Grid>
<b:ContentToggle Name="Toggle" Height="30"
Content="{Binding options, RelativeSource={StaticResource View}}"
Style="{DynamicResource LocalButtonStyle}"
/>
</Grid>
</Window>
In ButtonStyle.xaml...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--Custom Button backgrounds-->
<LinearGradientBrush x:Key="Button.Static.Background"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF2C0606" Offset="1"/>
<GradientStop Color="#E6ADAD"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="Button.MouseOver.Background" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF2C0606" Offset="1"/>
<GradientStop Color="#FFF2F2"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="Button.MouseOver.Checked.Background" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF2C0606" Offset="1"/>
<GradientStop Color="#F2FFF3"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="Button.Checked.Background" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF2C0606" Offset="1"/>
<GradientStop x:Name="GradientStop" Color="#ADE6B1"/>
</LinearGradientBrush>
<!--Establish the style colours-->
<SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070" />
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1" />
<SolidColorBrush x:Key="Button.Pressed.Background" Color="#C4F6CE" />
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B" />
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4" />
<SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5" />
<SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383" />
<!--Custom Style-->
<Style x:Key="LocalButtonStyle" TargetType="{x:Type ButtonBase}" BasedOn="{StaticResource ButtonStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background"
Value="{StaticResource Button.Static.Background}"/>
<Setter Property="BorderBrush"
Value="{StaticResource Button.Static.Border}"/>
</Style>
</ResourceDictionary>