infiniti96
infiniti96

Reputation: 98

Setting a style to wpf custom control overrides everything

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

Answers (2)

thatguy
thatguy

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

user2250152
user2250152

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

Related Questions