diff --git a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs
index 2eddac0c..6b213f2a 100644
--- a/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs
+++ b/components/Behaviors/src/Keyboard/KeyDownTriggerBehavior.cs
@@ -2,28 +2,35 @@
// 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.UI.Input;
using Microsoft.Xaml.Interactivity;
using Windows.System;
+using Windows.UI.Core;
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 +38,146 @@ 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 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.
+ ///
+ 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 (CheckModifierKeys && !CheckModifiers())
+ {
+ return;
+ }
+
+ keyRoutedEventArgs.Handled = true;
+ Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs);
}
-}
+ ///
+ /// Checks whether all required modifier keys specified in
+ /// are currently pressed. Retrieves the physical key states once and evaluates
+ /// them against the required modifier flags.
+ ///
+ /// if the current modifier state matches the requirements; otherwise, .
+ private bool CheckModifiers()
+ {
+ 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);
+ }
+
+ ///
+ /// Compares whether a specific modifier flag is required and whether the
+ /// corresponding key is currently pressed.
+ ///
+ /// 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)
+ {
+ bool required = (Modifiers & mod) != 0;
+ return required == isDown;
+ }
+}