diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs
index 97d08b5620..739ff72e51 100644
--- a/src/Avalonia.Base/Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs
@@ -176,9 +176,9 @@ namespace Avalonia.Input
/// The event args.
protected virtual void OnPreviewKeyDown(object? sender, KeyEventArgs e)
{
- // if the owner (IInputRoot) does not have the keyboard focus, ignore all keyboard events
+ // if the event did not originate from within the owner (IInputRoot), ignore all keyboard events
// KeyboardDevice.IsKeyboardFocusWithin in case of a PopupRoot seems to only work once, so we created our own
- var isFocusWithinOwner = IsFocusWithinOwner(_owner!);
+ var isFocusWithinOwner = IsFocusWithinOwner(_owner!, e.Source as IInputElement);
if (!isFocusWithinOwner)
return;
@@ -226,9 +226,9 @@ namespace Avalonia.Input
/// The event args.
protected virtual void OnKeyDown(object? sender, KeyEventArgs e)
{
- // if the owner (IInputRoot) does not have the keyboard focus, ignore all keyboard events
+ // if the event did not originate from within the owner (IInputRoot), ignore all keyboard events
// KeyboardDevice.IsKeyboardFocusWithin in case of a PopupRoot seems to only work once, so we created our own
- var isFocusWithinOwner = IsFocusWithinOwner(_owner!);
+ var isFocusWithinOwner = IsFocusWithinOwner(_owner!, e.Source as IInputElement);
if (!isFocusWithinOwner)
return;
@@ -450,18 +450,17 @@ namespace Avalonia.Input
}
///
- /// Checks if the focused element is a descendent of the owner.
+ /// Checks if the event's source is the owner itself, or a descendant of it.
///
/// The owner to check.
- /// If focused element is decendant of owner true, otherwise false.
- private static bool IsFocusWithinOwner(IInputElement owner)
+ /// The source of the key event, representing the effective focused element.
+ /// If is or a descendant of it, true, otherwise false.
+ private static bool IsFocusWithinOwner(InputElement owner, IInputElement? source)
{
- var focusedElement = KeyboardDevice.Instance?.FocusedElement;
- if (focusedElement is not InputElement inputElement)
+ if (source is not Visual sourceVisual)
return false;
-
- var isAncestorOf = owner is Visual root && root.IsVisualAncestorOf(inputElement);
- return isAncestorOf;
+
+ return ReferenceEquals(sourceVisual, owner) || owner.IsVisualAncestorOf(sourceVisual);
}
///
diff --git a/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs b/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
index 166b777ba1..557459d9af 100644
--- a/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
@@ -131,11 +131,12 @@ namespace Avalonia.Base.UnitTests.Input
KeyUp(root, Key.A, "a", KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
- // This differs from WPF which doesn't raise the `A` key event, but matches UWP.
+ // AccessKeyHandler marks the Alt+A KeyDown as Handled once it matches a registered
+ // access key, so a plain KeyDown subscriber doesn't see it. KeyUp is unaffected: only
+ // KeyUp for the Alt key itself is handled, not arbitrary registered access keys.
Assert.Equal(new[]
{
"KeyDown LeftAlt",
- "KeyDown A",
"KeyUp A",
"KeyUp LeftAlt",
}, events);
@@ -283,6 +284,97 @@ namespace Avalonia.Base.UnitTests.Input
}
}
+ [Fact]
+ public void Should_Raise_AccessKey_When_Nothing_Is_Focused()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var button = new Button();
+ var root = new TestRoot(button);
+ var target = new AccessKeyHandler();
+ var raised = 0;
+
+ Assert.Null(KeyboardDevice.Instance?.FocusedElement);
+
+ target.SetOwner(root);
+ target.Register("A", button);
+ button.AddHandler(AccessKeyHandler.AccessKeyEvent, (s, e) => ++raised);
+
+ KeyDown(root, Key.LeftAlt);
+ KeyDown(root, Key.A, "a", KeyModifiers.Alt);
+
+ Assert.Equal(1, raised);
+ }
+ }
+
+ [Fact]
+ public void Should_Raise_AccessKey_When_Owner_Itself_Is_Focused()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var button = new Button();
+ var root = new TestRoot(button);
+ var target = new AccessKeyHandler();
+ var raised = 0;
+
+ KeyboardDevice.Instance?.SetFocusedElement(root, NavigationMethod.Unspecified, KeyModifiers.None);
+
+ target.SetOwner(root);
+ target.Register("A", button);
+ button.AddHandler(AccessKeyHandler.AccessKeyEvent, (s, e) => ++raised);
+
+ KeyDown(root, Key.LeftAlt);
+ KeyDown(root, Key.A, "a", KeyModifiers.Alt);
+
+ Assert.Equal(1, raised);
+ }
+ }
+
+ [Fact]
+ public void Should_Open_MainMenu_On_Alt_KeyUp_When_Nothing_Is_Focused()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var target = new AccessKeyHandler();
+ var menu = new FakeMenu();
+ var root = new TestRoot(menu);
+
+ Assert.Null(KeyboardDevice.Instance?.FocusedElement);
+
+ target.SetOwner(root);
+ target.MainMenu = menu;
+
+ KeyDown(root, Key.LeftAlt);
+ Assert.Equal(0, menu.TimesOpenCalled);
+
+ KeyUp(root, Key.LeftAlt);
+ Assert.Equal(1, menu.TimesOpenCalled);
+ }
+ }
+
+ [Fact]
+ public void Should_Raise_AccessKey_When_Focus_Is_On_Descendant()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var button = new Button();
+ var root = new TestRoot(button);
+ var target = new AccessKeyHandler();
+ var raised = 0;
+
+ KeyboardDevice.Instance?.SetFocusedElement(button, NavigationMethod.Unspecified, KeyModifiers.None);
+
+ target.SetOwner(root);
+ target.Register("A", button);
+ button.AddHandler(AccessKeyHandler.AccessKeyEvent, (s, e) => ++raised);
+
+ KeyDown(button, Key.LeftAlt);
+ KeyDown(button, Key.A, "a", KeyModifiers.Alt);
+
+ Assert.Equal(1, raised);
+ }
+ }
+
private static void KeyDown(IInputElement target, Key key, string? keySymbol = null, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs