Skip to content

Commit 6b836a3

Browse files
Iryna KonovalovaIryna Konovalova
authored andcommitted
Improve KeyDownTriggerBehavior to support PreviewKeyDown, handled events, and modifier keys
1 parent b1d8231 commit 6b836a3

1 file changed

Lines changed: 105 additions & 16 deletions

File tree

components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,142 @@
88
namespace CommunityToolkit.WinUI.Behaviors;
99

1010
/// <summary>
11-
/// This behavior listens to a key down event on the associated <see cref="UIElement"/> when it is loaded and executes an action.
11+
/// A behavior that listens to <see cref="UIElement.PreviewKeyDown"/> on the associated
12+
/// <see cref="FrameworkElement"/> and executes its actions when the specified key and
13+
/// optional modifier keys are pressed. Supports capturing handled events.
1214
/// </summary>
1315
[TypeConstraint(typeof(FrameworkElement))]
1416
public class KeyDownTriggerBehavior : Trigger<FrameworkElement>
1517
{
1618
/// <summary>
17-
/// Identifies the <see cref="Key"/> property.
19+
/// Identifies the <see cref="Key"/> dependency property.
1820
/// </summary>
19-
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
20-
nameof(Key),
21-
typeof(VirtualKey),
22-
typeof(KeyDownTriggerBehavior),
23-
new PropertyMetadata(null));
21+
public static readonly DependencyProperty KeyProperty =
22+
DependencyProperty.Register(
23+
nameof(Key),
24+
typeof(VirtualKey),
25+
typeof(KeyDownTriggerBehavior),
26+
new PropertyMetadata(VirtualKey.None));
2427

2528
/// <summary>
26-
/// Gets or sets the key to listen when the associated object is loaded.
29+
/// Gets or sets the key that triggers the behavior.
2730
/// </summary>
2831
public VirtualKey Key
2932
{
3033
get => (VirtualKey)GetValue(KeyProperty);
3134
set => SetValue(KeyProperty, value);
3235
}
3336

37+
/// <summary>
38+
/// Identifies the <see cref="Modifiers"/> dependency property.
39+
/// </summary>
40+
public static readonly DependencyProperty ModifiersProperty =
41+
DependencyProperty.Register(
42+
nameof(Modifiers),
43+
typeof(VirtualKeyModifiers),
44+
typeof(KeyDownTriggerBehavior),
45+
new PropertyMetadata(VirtualKeyModifiers.None));
46+
47+
/// <summary>
48+
/// Gets or sets the modifier keys that must be pressed together with <see cref="Key"/>.
49+
/// </summary>
50+
public VirtualKeyModifiers Modifiers
51+
{
52+
get => (VirtualKeyModifiers)GetValue(ModifiersProperty);
53+
set => SetValue(ModifiersProperty, value);
54+
}
55+
56+
/// <summary>
57+
/// Identifies the <see cref="HandledEventsToo"/> dependency property.
58+
/// </summary>
59+
public static readonly DependencyProperty HandledEventsTooProperty =
60+
DependencyProperty.Register(
61+
nameof(HandledEventsToo),
62+
typeof(bool),
63+
typeof(KeyDownTriggerBehavior),
64+
new PropertyMetadata(true));
65+
66+
/// <summary>
67+
/// Gets or sets a value indicating whether the behavior should receive
68+
/// <see cref="UIElement.PreviewKeyDown"/> events even if they were already handled.
69+
/// </summary>
70+
public bool HandledEventsToo
71+
{
72+
get => (bool)GetValue(HandledEventsTooProperty);
73+
set => SetValue(HandledEventsTooProperty, value);
74+
}
75+
3476
/// <inheritdoc/>
3577
protected override void OnAttached()
3678
{
37-
AssociatedObject.KeyDown += OnAssociatedObjectKeyDown;
79+
AssociatedObject.AddHandler(
80+
UIElement.PreviewKeyDownEvent,
81+
new KeyEventHandler(OnPreviewKeyDown),
82+
HandledEventsToo);
3883
}
3984

4085
/// <inheritdoc/>
4186
protected override void OnDetaching()
4287
{
43-
AssociatedObject.KeyDown -= OnAssociatedObjectKeyDown;
88+
AssociatedObject.RemoveHandler(
89+
UIElement.PreviewKeyDownEvent,
90+
new KeyEventHandler(OnPreviewKeyDown));
4491
}
4592

4693
/// <summary>
47-
/// Invokes the current actions when the <see cref="Key"/> is pressed.
94+
/// Handles the <see cref="UIElement.PreviewKeyDown"/> event and executes the associated actions
95+
/// when the specified <see cref="Key"/> and <see cref="Modifiers"/> match.
4896
/// </summary>
4997
/// <param name="sender">The source <see cref="UIElement"/> instance.</param>
5098
/// <param name="keyRoutedEventArgs">The arguments for the event (unused).</param>
51-
private void OnAssociatedObjectKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs)
99+
private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs)
52100
{
53-
if (keyRoutedEventArgs.Key == Key)
101+
if (keyRoutedEventArgs.Key != Key)
54102
{
55-
keyRoutedEventArgs.Handled = true;
56-
Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs);
103+
return;
57104
}
105+
106+
if (!CheckModifiers())
107+
{
108+
return;
109+
}
110+
111+
keyRoutedEventArgs.Handled = true;
112+
Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs);
58113
}
59-
}
60114

115+
/// <summary>
116+
/// Checks whether all required modifier keys specified in <see cref="Modifiers"/>
117+
/// are currently pressed.
118+
/// </summary>
119+
/// <returns><see langword="true"/> if the modifier state matches; otherwise, <see langword="false"/>.</returns>
120+
121+
private bool CheckModifiers() =>
122+
Match(VirtualKeyModifiers.Control, VirtualKey.Control) &&
123+
Match(VirtualKeyModifiers.Shift, VirtualKey.Shift) &&
124+
Match(VirtualKeyModifiers.Menu, VirtualKey.Menu);
125+
126+
/// <summary>
127+
/// Determines whether a specific modifier key is required and whether it is currently pressed.
128+
/// </summary>
129+
/// <param name="mod">The modifier flag to test.</param>
130+
/// <param name="key">The physical key corresponding to the modifier.</param>
131+
/// <returns><see langword="true"/> if the modifier requirement matches the current key state; otherwise, <see langword="false"/>.</returns>
132+
private bool Match(VirtualKeyModifiers mod, VirtualKey key)
133+
{
134+
bool required = (Modifiers & mod) != 0;
135+
bool pressed = IsDown(key);
136+
return required == pressed;
137+
}
138+
139+
/// <summary>
140+
/// Checks whether the specified key is currently in the <see cref="CoreVirtualKeyStates.Down"/> state.
141+
/// </summary>
142+
/// <param name="key">The key to test.</param>
143+
/// <returns><see langword="true"/> if the key is pressed; otherwise, <see langword="false"/>.</returns>
144+
private static bool IsDown(VirtualKey key)
145+
{
146+
var state = InputKeyboardSource.GetKeyStateForCurrentThread(key);
147+
return state.HasFlag(CoreVirtualKeyStates.Down);
148+
}
149+
}

0 commit comments

Comments
 (0)