diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index e6a73822e9..838d2bd70b 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -1219,6 +1219,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.AccessText.get_AccessKey
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Primitives.OverlayPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
@@ -2131,6 +2137,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.AccessText.get_AccessKey
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Primitives.OverlayPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs
index 96a4847db8..758e28e07f 100644
--- a/src/Avalonia.Base/Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs
@@ -122,9 +122,11 @@ namespace Avalonia.Input
///
/// The access key.
/// The input element.
- public void Register(char accessKey, IInputElement element)
+ public void Register(string accessKey, IInputElement element)
{
- var key = NormalizeKey(accessKey.ToString());
+ ArgumentException.ThrowIfNullOrEmpty(accessKey);
+
+ var key = NormalizeKey(accessKey);
// remove dead elements with matching key
for (var i = _registrations.Count - 1; i >= 0; i--)
@@ -224,7 +226,7 @@ namespace Avalonia.Input
MainMenu?.IsOpen != true)
return;
- e.Handled = ProcessKey(e.Key.ToString(), e.Source as IInputElement);
+ e.Handled = ProcessKey(e.KeySymbol, e.Source as IInputElement);
}
@@ -287,8 +289,11 @@ namespace Avalonia.Input
/// The access key to process.
/// The element to get the targets which are in scope.
/// If there matches true, otherwise false.
- protected bool ProcessKey(string key, IInputElement? element)
+ protected bool ProcessKey(string? key, IInputElement? element)
{
+ if (string.IsNullOrEmpty(key))
+ return false;
+
key = NormalizeKey(key);
var senderInfo = GetTargetForElement(element, key);
// Find the possible targets matching the access key
@@ -503,20 +508,11 @@ namespace Avalonia.Input
///
internal class AccessKeyPressedEventArgs : RoutedEventArgs
{
- ///
- /// The constructor for AccessKeyPressed event args
- ///
- public AccessKeyPressedEventArgs()
- {
- RoutedEvent = AccessKeyHandler.AccessKeyPressedEvent;
- Key = null;
- }
-
///
/// Constructor for AccessKeyPressed event args
///
///
- public AccessKeyPressedEventArgs(string key) : this()
+ public AccessKeyPressedEventArgs(string key)
{
RoutedEvent = AccessKeyHandler.AccessKeyPressedEvent;
Key = key;
diff --git a/src/Avalonia.Base/Input/IAccessKeyHandler.cs b/src/Avalonia.Base/Input/IAccessKeyHandler.cs
index aaad93eb23..418fa61f05 100644
--- a/src/Avalonia.Base/Input/IAccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/IAccessKeyHandler.cs
@@ -26,7 +26,7 @@ namespace Avalonia.Input
///
/// The access key.
/// The input element.
- void Register(char accessKey, IInputElement element);
+ void Register(string accessKey, IInputElement element);
///
/// Unregisters the access keys associated with the input element.
diff --git a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs
index c98c5c9a22..5ecfb29afc 100644
--- a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Automation.Peers
{
if (Owner.HeaderPresenter?.Child is AccessText accessText)
{
- result = accessText.AccessKey.ToString();
+ result = accessText.AccessKey;
}
}
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index c5658fcd23..7f59c9e570 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -4,6 +4,7 @@ using Avalonia.Reactive;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using System;
+using System.Text;
namespace Avalonia.Controls.Primitives
{
@@ -42,7 +43,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets the access key.
///
- public char AccessKey
+ public string? AccessKey
{
get;
private set;
@@ -93,7 +94,7 @@ namespace Avalonia.Controls.Primitives
base.OnAttachedToVisualTree(e);
_accessKeys = (e.Root as TopLevel)?.AccessKeyHandler;
- if (_accessKeys != null && AccessKey != 0)
+ if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
_accessKeys.Register(AccessKey, this);
}
@@ -104,7 +105,7 @@ namespace Avalonia.Controls.Primitives
{
base.OnDetachedFromVisualTree(e);
- if (_accessKeys != null && AccessKey != 0)
+ if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
_accessKeys.Unregister(this);
_accessKeys = null;
@@ -153,7 +154,7 @@ namespace Avalonia.Controls.Primitives
/// The new text.
private void TextChanged(string? text)
{
- var key = (char)0;
+ string? key = null;
if (text != null)
{
@@ -161,13 +162,14 @@ namespace Avalonia.Controls.Primitives
if (underscore != -1 && underscore < text.Length - 1)
{
- key = text[underscore + 1];
+ var rune = Rune.GetRuneAt(text, underscore + 1);
+ key = rune.ToString();
}
}
AccessKey = key;
- if (_accessKeys != null && AccessKey != 0)
+ if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
_accessKeys.Register(AccessKey, this);
}
diff --git a/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs b/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
index 6cd8650011..9443e22855 100644
--- a/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
@@ -21,8 +21,8 @@ namespace Avalonia.Base.UnitTests.Input
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
- KeyDown(root, Key.A, KeyModifiers.Alt);
- KeyUp(root, Key.A, KeyModifiers.Alt);
+ KeyDown(root, Key.A, "a", KeyModifiers.Alt);
+ KeyUp(root, Key.A, "a", KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(new[]
@@ -48,8 +48,8 @@ namespace Avalonia.Base.UnitTests.Input
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
- KeyDown(root, Key.A, KeyModifiers.Alt);
- KeyUp(root, Key.A, KeyModifiers.Alt);
+ KeyDown(root, Key.A, "a", KeyModifiers.Alt);
+ KeyUp(root, Key.A, "a", KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(new[]
@@ -122,13 +122,13 @@ namespace Avalonia.Base.UnitTests.Input
var events = new List();
target.SetOwner(root);
- target.Register('A', button);
+ target.Register("A", button);
root.KeyDown += (s, e) => events.Add($"KeyDown {e.Key}");
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
- KeyDown(root, Key.A, KeyModifiers.Alt);
- KeyUp(root, Key.A, KeyModifiers.Alt);
+ KeyDown(root, Key.A, "a", KeyModifiers.Alt);
+ 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.
@@ -141,8 +141,12 @@ namespace Avalonia.Base.UnitTests.Input
}, events);
}
- [Fact]
- public void Should_Raise_AccessKey_For_Registered_Access_Key()
+ [Theory]
+ [InlineData("A", Key.A, "a")]
+ [InlineData("A", Key.Q, "a")]
+ [InlineData("é", Key.D2, "é")]
+ [InlineData("2", Key.D2, "2")]
+ public void Should_Raise_AccessKey_For_Registered_Access_Key_Matching_KeySymbol(string registered, Key key, string keySymbol)
{
using (UnitTestApplication.Start(TestServices.RealFocus))
{
@@ -154,22 +158,51 @@ namespace Avalonia.Base.UnitTests.Input
KeyboardDevice.Instance?.SetFocusedElement(button, NavigationMethod.Unspecified, KeyModifiers.None);
target.SetOwner(root);
- target.Register('A', button);
+ target.Register(registered, button);
button.AddHandler(AccessKeyHandler.AccessKeyEvent, (s, e) => ++raised);
KeyDown(root, Key.LeftAlt);
Assert.Equal(0, raised);
- KeyDown(root, Key.A, KeyModifiers.Alt);
+ KeyDown(root, key, keySymbol, KeyModifiers.Alt);
Assert.Equal(1, raised);
- KeyUp(root, Key.A, KeyModifiers.Alt);
+ KeyUp(root, key, keySymbol, KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(1, raised);
}
}
+ [Fact]
+ public void Should_Not_Raise_AccessKey_For_Registered_Access_Key_Not_Matching_KeySymbol()
+ {
+ 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, (_, _) => ++raised);
+
+ KeyDown(root, Key.LeftAlt);
+ Assert.Equal(0, raised);
+
+ KeyDown(root, Key.A, "q", KeyModifiers.Alt);
+ Assert.Equal(0, raised);
+
+ KeyUp(root, Key.A, "q", KeyModifiers.Alt);
+ KeyUp(root, Key.LeftAlt);
+
+ Assert.Equal(0, raised);
+ }
+ }
+
[Theory]
[InlineData(false, 0)]
[InlineData(true, 1)]
@@ -185,16 +218,16 @@ namespace Avalonia.Base.UnitTests.Input
KeyboardDevice.Instance?.SetFocusedElement(button, NavigationMethod.Unspecified, KeyModifiers.None);
target.SetOwner(root);
- target.Register('A', button);
+ target.Register("A", button);
button.AddHandler(AccessKeyHandler.AccessKeyEvent, (s, e) => ++raised);
KeyDown(root, Key.LeftAlt);
Assert.Equal(0, raised);
- KeyDown(root, Key.A, KeyModifiers.Alt);
+ KeyDown(root, Key.A, "a", KeyModifiers.Alt);
Assert.Equal(expected, raised);
- KeyUp(root, Key.A, KeyModifiers.Alt);
+ KeyUp(root, Key.A, "a", KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(expected, raised);
}
@@ -223,22 +256,24 @@ namespace Avalonia.Base.UnitTests.Input
}
}
- private static void KeyDown(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
+ private static void KeyDown(IInputElement target, Key key, string? keySymbol = null, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key,
+ KeySymbol = keySymbol,
KeyModifiers = modifiers,
});
}
- private static void KeyUp(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
+ private static void KeyUp(IInputElement target, Key key, string? keySymbol = null, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyUpEvent,
Key = key,
+ KeySymbol = keySymbol,
KeyModifiers = modifiers,
});
}
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index c8b985afda..661791a6e4 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -345,16 +345,17 @@ namespace Avalonia.Controls.UnitTests
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded, TestContext.Current.CancellationToken);
- var accessKey = Key.A;
+ const Key accessKey = Key.A;
+ const string accessKeySymbol = "a";
target.CommandParameter = true;
- RaiseAccessKey(root, accessKey);
+ RaiseAccessKey(root, accessKey, accessKeySymbol);
Assert.Equal(1, raised);
target.CommandParameter = false;
- RaiseAccessKey(root, accessKey);
+ RaiseAccessKey(root, accessKey, accessKeySymbol);
Assert.Equal(1, raised);
@@ -379,30 +380,32 @@ namespace Avalonia.Controls.UnitTests
return topLevel;
}
- static void RaiseAccessKey(IInputElement target, Key accessKey)
+ static void RaiseAccessKey(IInputElement target, Key accessKey, string keySymbol)
{
KeyDown(target, Key.LeftAlt);
- KeyDown(target, accessKey, KeyModifiers.Alt);
- KeyUp(target, accessKey, KeyModifiers.Alt);
- KeyUp(target, Key.LeftAlt);
+ KeyDown(target, accessKey, keySymbol, KeyModifiers.Alt);
+ KeyUp(target, accessKey, keySymbol, KeyModifiers.Alt);
+ KeyUp(target, Key.LeftAlt, null);
}
- static void KeyDown(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
+ static void KeyDown(IInputElement target, Key key, string? keySymbol = null, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key,
+ KeySymbol = keySymbol,
KeyModifiers = modifiers,
});
}
- static void KeyUp(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
+ static void KeyUp(IInputElement target, Key key, string? keySymbol = null, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyUpEvent,
Key = key,
+ KeySymbol = keySymbol,
KeyModifiers = modifiers,
});
}
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index 3ea591d361..a4cdf592cc 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -718,10 +718,10 @@ namespace Avalonia.Controls.UnitTests
}
[Theory]
- [InlineData(Key.A, 1)]
- [InlineData(Key.L, 2)]
- [InlineData(Key.D, 0)]
- public void Should_TabControl_Recognizes_AccessKey(Key accessKey, int selectedTabIndex)
+ [InlineData(Key.A, "a", 1)]
+ [InlineData(Key.L, "l", 2)]
+ [InlineData(Key.D, "d", 0)]
+ public void Should_TabControl_Recognizes_AccessKey(Key accessKey, string accessKeySymbol, int selectedTabIndex)
{
var ah = new AccessKeyHandler();
var kd = new KeyboardDevice();
@@ -760,8 +760,8 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(tabControl);
KeyDown(root, Key.LeftAlt);
- KeyDown(root, accessKey, KeyModifiers.Alt);
- KeyUp(root, accessKey, KeyModifiers.Alt);
+ KeyDown(root, accessKey, accessKeySymbol, KeyModifiers.Alt);
+ KeyUp(root, accessKey, accessKeySymbol, KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(selectedTabIndex, tabControl.SelectedIndex);
@@ -788,22 +788,24 @@ namespace Avalonia.Controls.UnitTests
return topLevel;
}
- static void KeyDown(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
+ static void KeyDown(IInputElement target, Key key, string? keySymbol = null, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key,
+ KeySymbol = keySymbol,
KeyModifiers = modifiers,
});
}
- static void KeyUp(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
+ static void KeyUp(IInputElement target, Key key, string? keySymbol = null, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyUpEvent,
Key = key,
+ KeySymbol = keySymbol,
KeyModifiers = modifiers,
});
}