Skip to content

Commit 8b01b15

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

1 file changed

Lines changed: 113 additions & 16 deletions

File tree

components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs

Lines changed: 113 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,150 @@
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
{
18+
private KeyEventHandler? _handler;
1619
/// <summary>
17-
/// Identifies the <see cref="Key"/> property.
20+
/// Identifies the <see cref="Key"/> dependency property.
1821
/// </summary>
19-
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
20-
nameof(Key),
21-
typeof(VirtualKey),
22-
typeof(KeyDownTriggerBehavior),
23-
new PropertyMetadata(null));
22+
public static readonly DependencyProperty KeyProperty =
23+
DependencyProperty.Register(
24+
nameof(Key),
25+
typeof(VirtualKey),
26+
typeof(KeyDownTriggerBehavior),
27+
new PropertyMetadata(VirtualKey.None));
2428

2529
/// <summary>
26-
/// Gets or sets the key to listen when the associated object is loaded.
30+
/// Gets or sets the key that triggers the behavior.
2731
/// </summary>
2832
public VirtualKey Key
2933
{
3034
get => (VirtualKey)GetValue(KeyProperty);
3135
set => SetValue(KeyProperty, value);
3236
}
3337

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

4088
/// <inheritdoc/>
4189
protected override void OnDetaching()
4290
{
43-
AssociatedObject.KeyDown -= OnAssociatedObjectKeyDown;
91+
if (_handler is not null)
92+
{
93+
AssociatedObject.RemoveHandler(
94+
UIElement.PreviewKeyDownEvent,
95+
_handler);
96+
97+
_handler = null;
98+
}
4499
}
45100

46101
/// <summary>
47-
/// Invokes the current actions when the <see cref="Key"/> is pressed.
102+
/// Handles the <see cref="UIElement.PreviewKeyDown"/> event and executes the associated actions
103+
/// when the specified <see cref="Key"/> and <see cref="Modifiers"/> match.
48104
/// </summary>
49105
/// <param name="sender">The source <see cref="UIElement"/> instance.</param>
50106
/// <param name="keyRoutedEventArgs">The arguments for the event (unused).</param>
51-
private void OnAssociatedObjectKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs)
107+
private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs)
52108
{
53-
if (keyRoutedEventArgs.Key == Key)
109+
if (keyRoutedEventArgs.Key != Key)
54110
{
55-
keyRoutedEventArgs.Handled = true;
56-
Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs);
111+
return;
57112
}
113+
114+
if (!CheckModifiers())
115+
{
116+
return;
117+
}
118+
119+
keyRoutedEventArgs.Handled = true;
120+
Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs);
58121
}
59-
}
60122

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

0 commit comments

Comments
 (0)