Reputation: 98
I am creating a split button where I am trying to set a default template to it so if the split button is to be used elsewhere outside of the control, it can be. The issue here is, is that when a user calls the split button into their control and they attach their style to it, it completely removes everything from the split button. I'm not entirely sure how to fix it. I would appreciate any help.
MySplitButton.xaml:
<local:SplitButton x:Class="WpfApp4.SplitButton.MySplitButton"
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:WpfApp4.SplitButton"
mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="100">
<local:SplitButton.Resources>
</local:SplitButton.Resources>
<local:SplitButton.Style>
<Style TargetType="{x:Type local:SplitButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SplitButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<local:LockableToggleButton Grid.Column="0">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
</local:LockableToggleButton>
<Button Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:SplitButton.Style>
</local:SplitButton>
MySplitButton.xaml.cs
public partial class MySplitButton : SplitButton
{
public MySplitButton()
{
InitializeComponent();
}
}
public class SplitButton : ToggleButton
{
public ICommand PrimaryButtonCommand
{
get { return (ICommand)GetValue(PrimaryButtonCommandProperty); }
set { SetValue(PrimaryButtonCommandProperty, value); }
}
public static readonly DependencyProperty PrimaryButtonCommandProperty;
public bool ToggleLock
{
get { return (bool)GetValue(ToggleLockProperty); }
set { SetValue(ToggleLockProperty, value); }
}
public static readonly DependencyProperty ToggleLockProperty;
public bool ContextMenuOpen
{
get { return (bool)GetValue(ContextMenuOpenProperty); }
set { SetValue(ContextMenuOpenProperty, value); }
}
public static readonly DependencyProperty ContextMenuOpenProperty;
static SplitButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(typeof(SplitButton)));
PrimaryButtonCommandProperty = DependencyProperty.Register("PrimaryButtonCommand", typeof(ICommand), typeof(SplitButton), new FrameworkPropertyMetadata(null));
ToggleLockProperty = DependencyProperty.Register("ToggleLock", typeof(bool), typeof(SplitButton), new UIPropertyMetadata(false));
ContextMenuOpenProperty = DependencyProperty.Register("ContextMenuOpen", typeof(bool), typeof(SplitButton), new FrameworkPropertyMetadata(false));
}
}
public class LockableToggleButton : ToggleButton
{
public bool ToggleLock
{
get { return (bool)GetValue(ToggleLockProperty); }
set { SetValue(ToggleLockProperty, value); }
}
public static readonly DependencyProperty ToggleLockProperty =
DependencyProperty.Register("ToggleLock", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
protected override void OnToggle()
{
if (!ToggleLock)
{
base.OnToggle();
}
}
}
So when I call MySplitButton
on MainWindow
like this, and attach a style to it, everything gets overridden and I don't know what I am doing wrong:
<Window x:Class="WpfApp4.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp4"
xmlns:cc="clr-namespace:WpfApp4.SplitButton"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:MainWindow.PrimaryButtonUICommand}" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Window.Resources>
<Style x:Key="thisStyle" TargetType="{x:Type cc:SplitButton}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</Window.Resources>
<Grid>
<cc:MySplitButton x:Name="SplitButton" Margin="346,197,345,188" Style="{DynamicResource thisStyle}">
<StackPanel Orientation="Horizontal">
<Label Content="hello"/>
</StackPanel>
</cc:MySplitButton>
</Grid>
</Window>
Upvotes: 1
Views: 745
Reputation: 22079
The way that you created the XAML markup for your custom button, the style will get instantiated and applied to a MySplitButton
instance when it is created. Specifying a TargetType
on a style does not automatically inherit the default style. You can base a style on another using the BasedOn
attribute. However, you cannot reference your default style, since it is only available on an instance of MySplitButton
. The solution is to extract the default style to a resource dictionary to share it.
Usually, when creating custom controls, you would create a dedicated assembly and create a resource dictionary called Generic.xaml
in a Themes
folder. This resource dictionary contains your default control styles. Note that the TargetType
is MySplitButton
, because that is your custom control, not SplitButton
. Since there is no x:Key
, this style is implicit and will be applied to all MySplitButton
controls in scope automatically.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControlLibrary">
<!-- Default style for your split button -->
<Style TargetType="{x:Type local:MySplitButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MySplitButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<local:LockableToggleButton Grid.Column="0">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
</local:LockableToggleButton>
<Button Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ...styles, templates and resources for other controls. -->
</ResourceDictionary>
In other projects you have to include the resources in the application resources, or at least in any resource dictionary in a scope where you use the controls. Otherwise, the style cannot be resolved.
<Application x:Class="MyApplication"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MyControlLibrary;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Specifying a TargetType
does not automatically inherit a style of a control.
Gets or sets the type for which this style is intended.
In order to base one style on another, you have to specify the base style through the BasedOn
attribute.
Gets or sets a defined style that is the basis of the current style. [...] When you use this property, the new style will inherit the values of the original style that are not explicitly redefined in the new style.
Consequently, you have to adapt your new thisStyle
like below.
<Style x:Key="thisStyle" TargetType="{x:Type cc:MySplitButton}" BasedOn="{StaticResource {x:Type cc:MySplitButton}}">
<Setter Property="Background" Value="Yellow"/>
</Style>
Remember, your original SplitButton
style must be available in the current scope, so your users must make sure to include the corresponding resource dictionary in their library or application.
Upvotes: 1
Reputation: 20625
Try to use BasedOn
.
<Window.Resources>
<Style x:Key="thisStyle" TargetType="{x:Type cc:SplitButton}" BasedOn="{StaticResource {x:Type cc:SplitButton}}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</Window.Resources>
Upvotes: 0