【问题标题】:Setting a style to wpf custom control overrides everything将样式设置为 wpf 自定义控件会覆盖所有内容
【发布时间】:2020-12-07 20:22:22
【问题描述】:

我正在创建一个拆分按钮,我正在尝试为其设置默认模板,因此如果拆分按钮要在控件之外的其他地方使用,它可以。这里的问题是,当用户将拆分按钮调用到他们的控件中并将他们的样式附加到它时,它会完全删除拆分按钮中的所有内容。我不完全确定如何解决它。我将不胜感激。

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();
            }
        }
    }

所以当我像这样在MainWindow 上调用MySplitButton 并为其附加样式时,一切都会被覆盖,我不知道自己做错了什么:

<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>

【问题讨论】:

    标签: c# wpf xaml data-binding custom-controls


    【解决方案1】:

    尝试使用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>
    

    【讨论】:

      【解决方案2】:

      按照您为自定义按钮创建 XAML 标记的方式,样式将在创建时被实例化并应用于 MySplitButtoninstance。在样式上指定 TargetType 不会自动继承默认样式。您可以使用BasedOn 属性基于另一种样式。但是,您不能引用您的默认样式,因为它仅在 MySplitButton 的实例上可用。解决方法是将默认样式提取到资源字典中进行共享。


      通常,在创建自定义控件时,您将创建一个专用程序集并在 Themes 文件夹中创建一个名为 Generic.xaml 的资源字典。此资源字典包含您的默认控件样式。请注意TargetTypeMySplitButton,因为这是您的自定义控件,而不是SplitButton。由于没有x:Key,因此该样式是隐式的,将自动应用于范围内的所有MySplitButton 控件。

      <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>
      

      在其他项目中,您必须将资源包含在应用程序资源中,或者至少包含在您使用控件的范围内的任何资源字典中。否则无法解析样式。

      <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>
      

      指定TargetType 不会自动继承控件的样式。

      获取或设置此样式所针对的类型。

      为了使一种样式基于另一种样式,您必须通过BasedOn 属性指定基本样式。

      获取或设置作为当前样式基础的已定义样式。 [...] 当您使用此属性时,新样式将继承原始样式的值在新样式中未明确重新定义

      因此,您必须调整新的thisStyle,如下所示。

      <Style x:Key="thisStyle" TargetType="{x:Type cc:MySplitButton}" BasedOn="{StaticResource {x:Type cc:MySplitButton}}">
         <Setter Property="Background" Value="Yellow"/>
      </Style>
      

      请记住,您的原始 SplitButton 样式必须在当前范围内可用,因此您的用户必须确保在他们的库或应用程序中包含相应的资源字典。

      【讨论】: