Cool Blue Cool Blue - 6 days ago 4
C# Question

How to create a default style for a common base class

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>


Solution Structure (common to both cases)



enter image description here

Evk Evk
Answer

I don't exactly know why, but for ButtonBase theme search does not work at all (probably related to it being abstract). Even if you try to find a style for ButtonBase with for example FindResource or similar methods - you won't find anything, even if you define it yourself in Generic.xaml (for other controls like Button you will find a style).

Now, to solve your particular problem easiest way I see is define style for ContentTogger but define template for ButtonBase (you can move it out in separate resource and reuse):

<Style TargetType="{x:Type c:ContentToggle}">
    <Setter Property="Template">
        <Setter.Value>
            <!-- move this template out to separate resource -->
            <ControlTemplate TargetType="{x:Type ButtonBase}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
Comments