触发器 setter 在更改后不会为 DependencyProperty 设置新值

Trigger setter doesn't set new value to DependencyProperty after it changed

我真的试图找到解决方案,但我失败了。所以我在单独的 xaml 文件中有 ResourceDictionary,在另一个 cs 文件中有 Control class。这是 xaml 代码:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApp1" x:Class="Control1">
    <Style TargetType="{x:Type local:Control1}">
        <Setter Property="GridColor" Value="Red"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:Control1}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <Grid x:Name="PART_Grid" Background="{TemplateBinding GridColor}" 
                              Height="{TemplateBinding Height}" Width="{TemplateBinding Width}"/>
                        <Button Grid.Column="1" x:Name ="PART_Button" Width="50" Height="50"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="GridColor" Value="Black"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

在 cs 文件中,我将 IsChecked 更改为 OnMouseEnterOnMouseLeave 事件处理程序。它工作正常。问题是,例如,当我在 OnButtonClick 事件处理程序中更改 GridColor 时,它会更改,但之后触发器 setter 不起作用(但另一个 setter 仍然工作正常)。没有例外,输出中没有消息。我错过了什么吗?

如果有人需要,这里是cs代码

  public class Control1: Control
    {
        public static readonly DependencyProperty GridColorProperty = 
            DependencyProperty.Register("GridColor", typeof(Brush), typeof(Control1), new PropertyMetadata(new SolidColorBrush(Colors.Red)));

        public static readonly DependencyProperty IsCheckedProperty = 
            DependencyProperty.Register("IsChecked", typeof(bool), typeof(Control1), new PropertyMetadata(false));

        public Brush GridColor
        {
            get { return (Brush)GetValue(GridColorProperty); }
            set { SetValue(GridColorProperty, value); }
        }

        public bool IsChecked
        {
            get { return (bool)GetValue(IsCheckedProperty); }
            set { SetValue(IsCheckedProperty, value); }
        }

        public override void OnApplyTemplate()
        {
            ((Grid)GetTemplateChild("PART_Grid")).MouseEnter += Grid_MouseEnter;
            ((Grid)GetTemplateChild("PART_Grid")).MouseLeave += Grid_MouseLeave;
            ((Button)GetTemplateChild("PART_Button")).Click += Button_Click;
            base.OnApplyTemplate();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            GridColor = new SolidColorBrush(Colors.DarkBlue);
        }

        private void Grid_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            IsChecked = false;
        }

        private void Grid_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            IsChecked = true;
        }
    }

您似乎 运行 遇到了 dependency property value precedence 的问题:在我看来您正在点击处理程序中设置本地值(链接优先级列表中的#3),并且覆盖由控件模板触发器设置的值(优先列表中的#4a)。

您唯一想要混合优先级的情况是当您明确想要替换以默认样式或基础 class 或类似性质完成的内容时。

但您的方法无论如何都不是最好的主意:理想情况下,您编写 WPF 控件时就好像无法访问源代码的陌生人必须编写默认样式和默认模板一样。然后你像陌生人一样编写你的默认模板。模板可能需要知道的任何内容都应该清楚地公开为 属性。

这并不总是可行的,但我将演示如何在这种情况下做到这一点。

蓝色笔刷表示一种状态:按钮已被点击。该状态未显式存储或暴露在任何地方,它只是隐含在 GridColor 属性 的值中(您实际上应该将其命名为 GridBrushGridBackground,因为它是不是 Color)。

因此,让我们为该状态添加一个 read-only 依赖项 属性,并将有关笔刷颜色的决定推到它所属的模板中。这真的很强大,因为如果你想让一些兄弟控件根据我们的 HasBeenClicked 状态改变 它的 状态,你可以只在父视图中添加一个绑定。

如果您希望控件的使用者能够以编程方式或通过绑定更改该状态,您还可以使 HasBeenClicked 成为常规 read/write 依赖项 属性。

//  This is not a good name, but I don't know what your semantics are. 
public bool HasBeenClicked
{
    get { return (bool)GetValue(HasBeenClickedProperty); }
    protected set { SetValue(HasBeenClickedPropertyKey, value); }
}

internal static readonly DependencyPropertyKey HasBeenClickedPropertyKey =
    DependencyProperty.RegisterReadOnly(nameof(HasBeenClicked), typeof(bool), typeof(Control1),
        new PropertyMetadata(false));

public static readonly DependencyProperty HasBeenClickedProperty = HasBeenClickedPropertyKey.DependencyProperty;

private void Button_Click(object sender, RoutedEventArgs e)
{
    HasBeenClicked = true;
}

并且在控件模板中:

<ControlTemplate.Triggers>
    <!-- 
    The last applicable trigger wins: If both are true, GridColor will be Black 
    -->
    <Trigger Property="HasBeenClicked" Value="True">
        <Setter Property="GridColor" Value="DarkBlue"/>
    </Trigger>
    <Trigger Property="IsChecked" Value="True">
        <Setter Property="GridColor" Value="Black"/>
    </Trigger>
</ControlTemplate.Triggers>