Use KeySymbol for AccessKey (#20662)
* Change AccessText.AccessKey to a string * Use KeySymbol for AccessKey * Update API suppressions * Fix tests
This commit is contained in:
committed by
GitHub
parent
d9a6ae517f
commit
2185ce04e7
@@ -1219,6 +1219,12 @@
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Controls.Primitives.AccessText.get_AccessKey</Target>
|
||||
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
|
||||
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>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})</Target>
|
||||
@@ -2131,6 +2137,12 @@
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:Avalonia.Controls.Primitives.AccessText.get_AccessKey</Target>
|
||||
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
|
||||
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>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})</Target>
|
||||
|
||||
@@ -122,9 +122,11 @@ namespace Avalonia.Input
|
||||
/// </summary>
|
||||
/// <param name="accessKey">The access key.</param>
|
||||
/// <param name="element">The input element.</param>
|
||||
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
|
||||
/// <param name="key">The access key to process.</param>
|
||||
/// <param name="element">The element to get the targets which are in scope.</param>
|
||||
/// <returns>If there matches <c>true</c>, otherwise <c>false</c>.</returns>
|
||||
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
|
||||
/// </summary>
|
||||
internal class AccessKeyPressedEventArgs : RoutedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The constructor for AccessKeyPressed event args
|
||||
/// </summary>
|
||||
public AccessKeyPressedEventArgs()
|
||||
{
|
||||
RoutedEvent = AccessKeyHandler.AccessKeyPressedEvent;
|
||||
Key = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for AccessKeyPressed event args
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public AccessKeyPressedEventArgs(string key) : this()
|
||||
public AccessKeyPressedEventArgs(string key)
|
||||
{
|
||||
RoutedEvent = AccessKeyHandler.AccessKeyPressedEvent;
|
||||
Key = key;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Avalonia.Input
|
||||
/// </summary>
|
||||
/// <param name="accessKey">The access key.</param>
|
||||
/// <param name="element">The input element.</param>
|
||||
void Register(char accessKey, IInputElement element);
|
||||
void Register(string accessKey, IInputElement element);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the access keys associated with the input element.
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Avalonia.Automation.Peers
|
||||
{
|
||||
if (Owner.HeaderPresenter?.Child is AccessText accessText)
|
||||
{
|
||||
result = accessText.AccessKey.ToString();
|
||||
result = accessText.AccessKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Gets the access key.
|
||||
/// </summary>
|
||||
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
|
||||
/// <param name="text">The new text.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<string>();
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user