From 8b01b152b9e43c472c9115cca16c51edddf33b91 Mon Sep 17 00:00:00 2001 From: Iryna Konovalova Date: Wed, 13 May 2026 16:46:59 +0200 Subject: [PATCH 1/5] 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); + } +} From 1163694b55e14b3dbbf4c939698a3b0e66c0597e Mon Sep 17 00:00:00 2001 From: Iryna Konovalova Date: Wed, 3 Jun 2026 22:09:16 +0200 Subject: [PATCH 2/5] Refactor modifier key handling in KeyDownTriggerBehavior - Consolidate modifier state retrieval into CheckModifiers - Inline IsDown logic into Match - Remove repeated GetKeyStateForCurrentThread calls - Update XML documentation to match new signatures --- .../src/Keyboard/KeyDownTriggerBehavior.cs | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs index 650b3b2a..3400df30 100644 --- a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs +++ b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs @@ -16,6 +16,7 @@ namespace CommunityToolkit.WinUI.Behaviors; public class KeyDownTriggerBehavior : Trigger { private KeyEventHandler? _handler; + /// /// Identifies the dependency property. /// @@ -122,36 +123,31 @@ private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventAr /// /// 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. + /// are currently pressed. Retrieves the physical key states once and evaluates + /// them against the required modifier flags. /// - /// 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) + /// if the current modifier state matches the requirements; otherwise, . + private bool CheckModifiers() { - bool required = (Modifiers & mod) != 0; - bool pressed = IsDown(key); - return required == pressed; + bool ctrl = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down); + bool shift = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down); + bool alt = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down); + + return Match(VirtualKeyModifiers.Control, ctrl) + && Match(VirtualKeyModifiers.Shift, shift) + && Match(VirtualKeyModifiers.Menu, alt); } /// - /// Checks whether the specified key is currently in the state. + /// Compares whether a specific modifier flag is required and whether the + /// corresponding key is currently pressed. /// - /// The key to test. - /// if the key is pressed; otherwise, . - private static bool IsDown(VirtualKey key) + /// The modifier flag to evaluate. + /// The current physical key state for that modifier. + /// if the requirement matches the key state; otherwise, . + private bool Match(VirtualKeyModifiers mod, bool isDown) { - var state = InputKeyboardSource.GetKeyStateForCurrentThread(key); - return state.HasFlag(CoreVirtualKeyStates.Down); + bool required = (Modifiers & mod) != 0; + return required == isDown; } } From 4be6b2175d1059ecd7ef27924daf76f974a01246 Mon Sep 17 00:00:00 2001 From: Iryna Konovalova Date: Thu, 4 Jun 2026 10:16:40 +0200 Subject: [PATCH 3/5] Add CheckModifierKeys property and preserve original behavior - Introduce CheckModifierKeys to avoid breaking change - Default value keeps legacy behavior unchanged - Enable modifier checking only when explicitly requested - Update XML documentation accordingly --- .../src/Keyboard/KeyDownTriggerBehavior.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs index 3400df30..9e20c7bf 100644 --- a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs +++ b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs @@ -55,6 +55,34 @@ public VirtualKeyModifiers Modifiers set => SetValue(ModifiersProperty, value); } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckModifierKeysProperty = + DependencyProperty.Register( + nameof(CheckModifierKeys), + typeof(bool), + typeof(KeyDownTriggerBehavior), + new PropertyMetadata(false)); + + /// + /// Gets or sets a value indicating whether the behavior should evaluate + /// the current modifier key state when matching the . + /// + /// When (default), the behavior ignores the state of + /// modifier keys and triggers solely based on the value, + /// preserving the original behavior. + /// + /// When , the behavior requires the modifier keys + /// specified in to match the current keyboard state + /// before triggering. + /// + public bool CheckModifierKeys + { + get => (bool)GetValue(CheckModifierKeysProperty); + set => SetValue(CheckModifierKeysProperty, value); + } + /// /// Identifies the dependency property. /// @@ -112,7 +140,7 @@ private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventAr return; } - if (!CheckModifiers()) + if (CheckModifierKeys && !CheckModifiers()) { return; } From 89a88e269dec6e58ca512ec0a64eb7cfefcb4fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D1=80=D0=B8=D0=BD=D0=B0=20=D0=9A=D0=BE=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=D0=BE=D0=B2=D0=B0?= <139651410+Irina-Konovalova@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:19:02 +0200 Subject: [PATCH 4/5] Update components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs Add missing namespace references required for compilation Co-authored-by: Avishai Dernis --- components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs index 9e20c7bf..4faca75d 100644 --- a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs +++ b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Xaml.Interactivity; +using Microsoft.UI.Input; using Microsoft.Xaml.Interactivity; using Windows.System; +using Windows.UI.Core; namespace CommunityToolkit.WinUI.Behaviors; From 5ad24e69ee187fe35d9c7a478e4971044ad18253 Mon Sep 17 00:00:00 2001 From: Iryna Konovalova Date: Thu, 4 Jun 2026 17:21:02 +0200 Subject: [PATCH 5/5] Remove duplicate using directive --- components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs index 4faca75d..6b213f2a 100644 --- a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs +++ b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Xaml.Interactivity; using Microsoft.UI.Input; using Microsoft.Xaml.Interactivity; using Windows.System;