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