Home > Software engineering >  How do i properly bind the IsPressed property to my command parameter?
How do i properly bind the IsPressed property to my command parameter?

Time:05-17

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.

CodePudding user response:

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.

CodePudding user response:

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 onm ouseLeftButtonDown(MouseButtonEventArgs e)
  {
    if (this.IsDoubleTriggerEnabled
      && this.ClickMode != ClickMode.Hover)
    {
      base.OnClick();
    }
    else
    {
      base.OnMouseLeftButtonDown(e);
    }
  }

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

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

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