diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index de753f0bd06..6ad4a8cc762 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -149,10 +149,10 @@ static HotKeyManager() return; var control = args.Sender as Control; - if (control is not IClickableControl) + if (control is not IClickableControl and not ICommandSource) { Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?.Log(control, - $"The element {args.Sender.GetType().Name} does not implement IClickableControl and does not support binding a HotKey ({args.NewValue})."); + $"The element {args.Sender.GetType().Name} does not implement IClickableControl nor ICommandSource and does not support binding a HotKey ({args.NewValue})."); return; } diff --git a/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs b/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs new file mode 100644 index 00000000000..55a3f0d5d4a --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs @@ -0,0 +1,122 @@ +using System; +using System.Windows.Input; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.LogicalTree; +using Avalonia.Platform; +using Avalonia.UnitTests; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + internal class HotKeyedTextBox : TextBox, ICommandSource + { + private class DelegateCommand : ICommand + { + private readonly Action _action; + public DelegateCommand(Action action) => _action = action; + public event EventHandler CanExecuteChanged { add { } remove { } } + public bool CanExecute(object parameter) => true; + public void Execute(object parameter) => _action(); + } + + public static readonly StyledProperty HotKeyProperty = + HotKeyManager.HotKeyProperty.AddOwner(); + + private KeyGesture _hotkey; + + public KeyGesture HotKey + { + get => GetValue(HotKeyProperty); + set => SetValue(HotKeyProperty, value); + } + + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + if (_hotkey != null) + { + this.SetValue(HotKeyProperty, _hotkey); + } + + base.OnAttachedToLogicalTree(e); + } + + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + if (this.HotKey != null) + { + _hotkey = this.HotKey; + this.SetValue(HotKeyProperty, null); + } + + base.OnDetachedFromLogicalTree(e); + } + + public void CanExecuteChanged(object sender, EventArgs e) + { + } + + protected override Type StyleKeyOverride => typeof(TextBox); + + public ICommand Command => _command; + + public object CommandParameter => null; + + private readonly DelegateCommand _command; + + public HotKeyedTextBox() + { + _command = new DelegateCommand(() => Focus()); + } + } + + public class HotKeyedControlsTests + { + private static Window PreparedWindow(object content = null) + { + var platform = AvaloniaLocator.Current.GetRequiredService(); + var windowImpl = Mock.Get(platform.CreateWindow()); + windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); + var w = new Window(windowImpl.Object) { Content = content }; + w.ApplyTemplate(); + return w; + } + + private static IDisposable CreateServicesWithFocus() + { + return UnitTestApplication.Start( + TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform( + null, + window => MockWindowingPlatform.CreatePopupMock(window).Object), + focusManager: new FocusManager(), + keyboardDevice: () => new KeyboardDevice())); + } + + [Fact] + public void HotKeyedTextBox_Focus_Performed_On_Hotkey() + { + using var _ = CreateServicesWithFocus(); + + var keyboardDevice = new KeyboardDevice(); + var hotKeyedTextBox = new HotKeyedTextBox { HotKey = new KeyGesture(Key.F, KeyModifiers.Control) }; + var root = PreparedWindow(); + root.Content = hotKeyedTextBox; + root.Show(); + + Assert.False(hotKeyedTextBox.IsFocused); + + keyboardDevice.ProcessRawEvent( + new RawKeyEventArgs( + keyboardDevice, + 0, + root, + RawKeyEventType.KeyDown, + Key.F, + RawInputModifiers.Control)); + + Assert.True(hotKeyedTextBox.IsFocused); + } + } +}