我如何正确地将 IsPressed 属性 绑定到我的命令参数?

How do i properly bind the IsPressed property to my command parameter?

我制作了一个自定义按钮,将命令绑定到(自定义,路由)IsPressedChanged 事件,以便在按下按钮和释放按钮时执行命令:

<local:CustomButton xmlns:i="http://schemas.microsoft.com/xaml/behaviors" x:Name="MyButton">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="CustomIsPressedChanged">
            <i:InvokeCommandAction Command="{Binding Path=SomeCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</local:CustomButton>

使用自定义按钮实现:

public partial class CustomButton : Button
    {
        /* Register a custom routed event using the bubble routing strategy. */
        public static readonly RoutedEvent CustomIsPressedChangedEvent = EventManager.RegisterRoutedEvent(
            name: "CustomIsPressedChanged",
            routingStrategy: RoutingStrategy.Bubble,
            handlerType: typeof(RoutedEventHandler),
            ownerType: typeof(CustomButton));

        /* Provide CLR accessors for assigning an event handler. */
        public event RoutedEventHandler CustomIsPressedChanged
        {
            add { AddHandler(CustomIsPressedChangedEvent, value); }
            remove { RemoveHandler(CustomIsPressedChangedEvent, value); }
        }

        public CustomButton() { InitializeComponent(); }

        /* Custom Event handling of the IsPressedChanged event */
        protected override void OnIsPressedChanged(System.Windows.DependencyPropertyChangedEventArgs e)
        {
            /* Call the base class OnIsPressedChanged() method so IsPressedChanged event subscribers are notified. */
            base.OnIsPressedChanged(e);

            /* Raise custom event */
            RaiseEvent(new RoutedEventArgs(routedEvent: CustomIsPressedChangedEvent));
        }
    }

这完全可以正常工作。

问题来了:

当我尝试将 IsPressed 属性 的值传播到命令时,如下所示:

<i:InvokeCommandAction Command="{Binding Path=SomeCommand}"
                       CommandParameter="{Binding ElementName=MyButton, Path=IsPressed}"/>

传播的值(看起来)总是 IsPressed 的旧值。当我按下按钮时,使用参数 beeing false 调用的命令,当我释放按钮时参数为真。 但是 当我在事件处理程序 CustomButton.OnIsPressedChanged() 中检查 IsPressed 的值时,它代表了预期的新值。

我的问题是: 我应该如何传播 IsPressed 的值以获得正确的值?是否保证总是使用旧值调用该命令?在那种情况下,我可以简单地反转值,但这对我来说似乎有点阴暗,我真的不想这样做,除非我知道它总是会产生正确的结果。

您可以将 DependencyPropertyChangedEventArgs 作为引发的 RoutedEventArgs 的参数传递:

protected override void OnIsPressedChanged(DependencyPropertyChangedEventArgs e)
{
    base.OnIsPressedChanged(e);

    // you may want to pass e.NewValue here for simplicity.
    RaiseEvent(new RoutedEventArgs(CustomIsPressedChangedEvent, e));
}

然后让InvokeCommandAction传给命令:

<i:InvokeCommandAction Command="{Binding Path=SomeCommand}"
                       PassEventArgsToCommand="True" />

然后,在命令中你只是需要转换传递的对象来检索IsPressed的新值:

SomeCommand = new ActionCommand(SomeCommandAction);

//...

private void SomeCommandAction(object o)
{
    if (o is not RoutedEventArgs routedEventArgs)
        return;

    if (routedEventArgs.OriginalSource is not DependencyPropertyChangedEventArgs eventArgs)
        return;

    if (eventArgs.NewValue is true)
        Count++;

    if (eventArgs.NewValue is false)
        Count--;

}

工作演示 here

为了方便起见(为了您的控件的使用),您不应执行并行命令。而是修改现有行为。

按钮有一个 Button.ClickMode 属性。按钮对此 属性 的内部过滤使得 Button 仅在 ClickMode.PressClickMode.ReleaseClickMode.Hover.
上执行一次 我们需要绕过此过滤以在 MouseLeftButtonDownMouseLeftButtonUp 上执行 Button.CommandButton.Click 事件(以实现 ClickMode.PressClickMode.Release)以及 MouseEnterMouseLeave(支持 ClickMode.Hover):

DoubleTriggerButton.cs

public class DoubleTriggerButton : Button
{
  public bool IsDoubleTriggerEnabled
  {
    get => (bool)GetValue(IsDoubleTriggerEnabledProperty);
    set => SetValue(IsDoubleTriggerEnabledProperty, value);
  }

  public static readonly DependencyProperty IsDoubleTriggerEnabledProperty = DependencyProperty.Register(
    "IsDoubleTriggerEnabled",
    typeof(bool),
    typeof(DoubleTriggerButton),
    new PropertyMetadata(true));

  protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  {
    if (this.IsDoubleTriggerEnabled
      && this.ClickMode != ClickMode.Hover)
    {
      base.OnClick();
    }
    else
    {
      base.OnMouseLeftButtonDown(e);
    }
  }

  protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
  {
    if (this.IsDoubleTriggerEnabled
      && this.ClickMode != ClickMode.Hover)
    {
      base.OnClick();
    }
    else
    {
      base.OnMouseLeftButtonUp(e);
    }
  }

  protected override void OnMouseEnter(MouseEventArgs e)
  {
    if (this.IsDoubleTriggerEnabled
      && this.ClickMode == ClickMode.Hover)
    {
      base.OnClick();
    }
    else
    {
      base.OnMouseEnter(e);
    }
  }

  protected override void OnMouseLeave(MouseEventArgs e)
  {
    if (this.IsDoubleTriggerEnabled
      && this.ClickMode == ClickMode.Hover)
    {
      base.OnClick();
    }
    else
    {
      base.OnMouseLeave(e);
    }
  }
}

我找到了另一个解决方案,与我的原始代码相比几乎不需要更改:

编辑:正如 BionicCode 在评论中指出的那样,由于多种原因,这不是一个好的设计。

通过向替换 IsPressed 属性 的 CustmButton 添加依赖项 属性,可以在 OnIsPressedChanged 事件处理程序中分配正确的值.绑定到新的 IsPressed 属性 然后像我预期的那样工作原来的 属性 工作:

public new static readonly DependencyProperty IsPressedProperty =
    DependencyProperty.Register("IsPressed", typeof(bool), typeof(CustomButton),
        new PropertyMetadata(false));

public new bool IsPressed
{
    get { return (bool)GetValue(IsPressedProperty); }
    set { SetValue(IsPressedProperty, value); }
}

protected override void OnIsPressedChanged(System.Windows.DependencyPropertyChangedEventArgs e)
{
    /* Call the base class OnIsPressedChanged() method so IsPressedChanged event subscribers are notified. */
    base.OnIsPressedChanged(e);

    /* Forward the value of the base.IsPressed property to the custom IsPressed property  */
    IsPressed = (bool)e.NewValue;

    /* Raise event */
    RaiseCustomRoutedEvent(new RoutedEventArgs(routedEvent: CustomIsPressedChangedEvent));
}

现在可以将命令参数与正在转发的新值绑定:

<local:CustomButton xmlns:i="http://schemas.microsoft.com/xaml/behaviors" x:Name="MyButton">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="CustomIsPressedChanged">
            <i:InvokeCommandAction Command="{Binding Path=SomeCommand}"
                                   CommandParameter="{Binding ElementName=MyButton, Path=IsPressed}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</local:CustomButton>