From 8b01b152b9e43c472c9115cca16c51edddf33b91 Mon Sep 17 00:00:00 2001 From: Iryna Konovalova Date: Wed, 13 May 2026 16:46:59 +0200 Subject: [PATCH] Improve KeyDownTriggerBehavior to support PreviewKeyDown, handled events, and modifier keys --- .../src/Keyboard/KeyDownTriggerBehavior.cs | 129 +++++++++++++++--- 1 file changed, 113 insertions(+), 16 deletions(-) diff --git a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs index 2eddac0c..650b3b2a 100644 --- a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs +++ b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs @@ -8,22 +8,26 @@ namespace CommunityToolkit.WinUI.Behaviors; /// -/// This behavior listens to a key down event on the associated when it is loaded and executes an action. +/// A behavior that listens to on the associated +/// and executes its actions when the specified key and +/// optional modifier keys are pressed. Supports capturing handled events. /// [TypeConstraint(typeof(FrameworkElement))] public class KeyDownTriggerBehavior : Trigger { + private KeyEventHandler? _handler; /// - /// Identifies the property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty KeyProperty = DependencyProperty.Register( - nameof(Key), - typeof(VirtualKey), - typeof(KeyDownTriggerBehavior), - new PropertyMetadata(null)); + public static readonly DependencyProperty KeyProperty = + DependencyProperty.Register( + nameof(Key), + typeof(VirtualKey), + typeof(KeyDownTriggerBehavior), + new PropertyMetadata(VirtualKey.None)); /// - /// Gets or sets the key to listen when the associated object is loaded. + /// Gets or sets the key that triggers the behavior. /// public VirtualKey Key { @@ -31,30 +35,123 @@ public VirtualKey Key set => SetValue(KeyProperty, value); } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ModifiersProperty = + DependencyProperty.Register( + nameof(Modifiers), + typeof(VirtualKeyModifiers), + typeof(KeyDownTriggerBehavior), + new PropertyMetadata(VirtualKeyModifiers.None)); + + /// + /// Gets or sets the modifier keys that must be pressed together with . + /// + public VirtualKeyModifiers Modifiers + { + get => (VirtualKeyModifiers)GetValue(ModifiersProperty); + set => SetValue(ModifiersProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HandledEventsTooProperty = + DependencyProperty.Register( + nameof(HandledEventsToo), + typeof(bool), + typeof(KeyDownTriggerBehavior), + new PropertyMetadata(true)); + + /// + /// Gets or sets a value indicating whether the behavior should receive + /// events even if they were already handled. + /// + public bool HandledEventsToo + { + get => (bool)GetValue(HandledEventsTooProperty); + set => SetValue(HandledEventsTooProperty, value); + } + /// protected override void OnAttached() { - AssociatedObject.KeyDown += OnAssociatedObjectKeyDown; + _handler = OnPreviewKeyDown; + + AssociatedObject.AddHandler( + UIElement.PreviewKeyDownEvent, + _handler, + HandledEventsToo); } /// protected override void OnDetaching() { - AssociatedObject.KeyDown -= OnAssociatedObjectKeyDown; + if (_handler is not null) + { + AssociatedObject.RemoveHandler( + UIElement.PreviewKeyDownEvent, + _handler); + + _handler = null; + } } /// - /// Invokes the current actions when the is pressed. + /// Handles the event and executes the associated actions + /// when the specified and match. /// /// The source instance. /// The arguments for the event (unused). - private void OnAssociatedObjectKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs) + private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs) { - if (keyRoutedEventArgs.Key == Key) + if (keyRoutedEventArgs.Key != Key) { - keyRoutedEventArgs.Handled = true; - Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs); + return; } + + if (!CheckModifiers()) + { + return; + } + + keyRoutedEventArgs.Handled = true; + Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs); } -} + /// + /// Checks whether all required modifier keys specified in + /// are currently pressed. + /// + /// if the modifier state matches; otherwise, . + + private bool CheckModifiers() => + Match(VirtualKeyModifiers.Control, VirtualKey.Control) && + Match(VirtualKeyModifiers.Shift, VirtualKey.Shift) && + Match(VirtualKeyModifiers.Menu, VirtualKey.Menu); + + /// + /// Determines whether a specific modifier key is required and whether it is currently pressed. + /// + /// The modifier flag to test. + /// The physical key corresponding to the modifier. + /// if the modifier requirement matches the current key state; otherwise, . + private bool Match(VirtualKeyModifiers mod, VirtualKey key) + { + bool required = (Modifiers & mod) != 0; + bool pressed = IsDown(key); + return required == pressed; + } + + /// + /// Checks whether the specified key is currently in the state. + /// + /// The key to test. + /// if the key is pressed; otherwise, . + private static bool IsDown(VirtualKey key) + { + var state = InputKeyboardSource.GetKeyStateForCurrentThread(key); + return state.HasFlag(CoreVirtualKeyStates.Down); + } +}