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

I've made a custom button to bind a command to a (custom, routed) IsPressedChanged event so that the command is executed both when the button is pressed AND when it is released:

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

With the custom button implementation:

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

This works perfectly as it should.

And now comes the Problem:

When I try to propagate the value of the IsPressed property to the command like so:

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

the propagated value will (seemingly) allways be the old value of IsPressed. When I press the button, the command called with the parameter beeing false, when I release the button the parameter is true. But when I check the value of IsPressed inside the event handler CustomButton.OnIsPressedChanged(), it represents the new value as expected.

My Question is: How should I propagate the value of IsPressed to get the correct value? Is it guaranteed that the command will always be called with the old value? In that case I could simply invert the value but that seems a bit shady to me and I really would not want to do this unless I know it will allways yield the correct result.



Solution 1:[1]

You can pass the DependencyPropertyChangedEventArgs as a parameter of the RoutedEventArgs that is raised:

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

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

Then ask the InvokeCommandAction to pass it to the command:

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

And then, in the command you just need to cast the passed object to retrieve the new value of 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--;

}

Working demo here.

Solution 2:[2]

For reasons of convenience (for the usage of your control), you should not implement a parallel command. Instead modify the existing behavior.

The button has a Button.ClickMode property. The button's internal filtering of this property makes the Button execute only once on either ClickMode.Press, ClickMode.Release or ClickMode.Hover.
We need to bypass this filtering to execute the Button.Command and the Button.Click event on both MouseLeftButtonDown and MouseLeftButtonUp (to implement ClickMode.Press and ClickMode.Release) as well as MouseEnter and MouseLeave (to support 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);
    }
  }
}

Solution 3:[3]

I have found another solution which barely needs changes compared to my original code:

Edit: As BionicCode pointed out in the comments, this is not a good design due to multiple reasons.

By adding a dependency property to the CustmButton which replaces the IsPressed property, one can assign the correct value inside the OnIsPressedChanged event handler. Binding to the new IsPressed property then works as I expected the original property to work:

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

One can now bind the command parameter with the new value beeing forwarded:

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 BionicCode
Solution 3