HotKeyManagerTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. using System;
  2. using Avalonia.Controls.Presenters;
  3. using Avalonia.Controls.Templates;
  4. using Avalonia.Input;
  5. using Avalonia.Input.Raw;
  6. using Avalonia.Platform;
  7. using Avalonia.UnitTests;
  8. using Xunit;
  9. using Factory = System.Func<int, System.Action<object>, Avalonia.Controls.Window, Avalonia.AvaloniaObject>;
  10. namespace Avalonia.Controls.UnitTests.Utils
  11. {
  12. public class HotKeyManagerTests : ScopedTestBase
  13. {
  14. [Fact]
  15. public void HotKeyManager_Should_Register_And_Unregister_Key_Binding()
  16. {
  17. using (AvaloniaLocator.EnterScope())
  18. {
  19. AvaloniaLocator.CurrentMutable
  20. .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
  21. var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
  22. var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control);
  23. var tl = new Window();
  24. var button = new Button();
  25. tl.Content = button;
  26. tl.Template = CreateWindowTemplate();
  27. tl.ApplyTemplate();
  28. tl.Presenter.ApplyTemplate();
  29. HotKeyManager.SetHotKey(button, gesture1);
  30. Assert.Equal(gesture1, tl.KeyBindings[0].Gesture);
  31. HotKeyManager.SetHotKey(button, gesture2);
  32. Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);
  33. tl.Content = null;
  34. tl.Presenter.ApplyTemplate();
  35. Assert.Empty(tl.KeyBindings);
  36. tl.Content = button;
  37. tl.Presenter.ApplyTemplate();
  38. Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);
  39. HotKeyManager.SetHotKey(button, null);
  40. Assert.Empty(tl.KeyBindings);
  41. }
  42. }
  43. [Theory]
  44. [MemberData(nameof(ElementsFactory), parameters: true)]
  45. public void HotKeyManager_Should_Use_CommandParameter(string factoryName, Factory factory)
  46. {
  47. using (AvaloniaLocator.EnterScope())
  48. {
  49. var target = new KeyboardDevice();
  50. var commandResult = 0;
  51. var expectedParameter = 1;
  52. AvaloniaLocator.CurrentMutable
  53. .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
  54. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  55. var action = new Action<object>(parameter =>
  56. {
  57. if (parameter is int value)
  58. {
  59. commandResult = value;
  60. }
  61. });
  62. var root = new Window();
  63. var element = factory(expectedParameter, action, root);
  64. root.Template = CreateWindowTemplate();
  65. root.ApplyTemplate();
  66. root.Presenter.ApplyTemplate();
  67. HotKeyManager.SetHotKey(element, gesture);
  68. target.ProcessRawEvent(new RawKeyEventArgs(target,
  69. 0,
  70. root,
  71. RawKeyEventType.KeyDown,
  72. Key.A,
  73. RawInputModifiers.Control,
  74. PhysicalKey.A,
  75. "a"));
  76. Assert.True(expectedParameter == commandResult, $"{factoryName} HotKey did not carry the CommandParameter.");
  77. }
  78. }
  79. [Theory]
  80. [MemberData(nameof(ElementsFactory), parameters: true)]
  81. public void HotKeyManager_Should_Do_Not_Executed_When_IsEnabled_False(string factoryName, Factory factory)
  82. {
  83. using (AvaloniaLocator.EnterScope())
  84. {
  85. var target = new KeyboardDevice();
  86. var isExecuted = false;
  87. AvaloniaLocator.CurrentMutable
  88. .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
  89. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  90. var action = new Action<object>(parameter =>
  91. {
  92. isExecuted = true;
  93. });
  94. var root = new Window();
  95. var element = factory(0, action, root) as InputElement;
  96. element.IsEnabled = false;
  97. root.Template = CreateWindowTemplate();
  98. root.ApplyTemplate();
  99. root.Presenter.ApplyTemplate();
  100. HotKeyManager.SetHotKey(element, gesture);
  101. target.ProcessRawEvent(new RawKeyEventArgs(target,
  102. 0,
  103. root,
  104. RawKeyEventType.KeyDown,
  105. Key.A,
  106. RawInputModifiers.Control,
  107. PhysicalKey.A,
  108. "a"));
  109. Assert.True(isExecuted == false, $"{factoryName} Execution raised when IsEnabled is false.");
  110. }
  111. }
  112. [Theory]
  113. [MemberData(nameof(ElementsFactory), parameters:false)]
  114. public void HotKeyManager_Should_Invoke_Event_Click_When_Command_Is_Null(string factoryName, Factory factory)
  115. {
  116. using (AvaloniaLocator.EnterScope())
  117. {
  118. var target = new KeyboardDevice();
  119. var clickExecutedCount = 0;
  120. AvaloniaLocator.CurrentMutable
  121. .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
  122. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  123. void Clickable_Click(object sender, Interactivity.RoutedEventArgs e)
  124. {
  125. clickExecutedCount++;
  126. }
  127. var root = new Window();
  128. var element = factory(0, default, root) as InputElement;
  129. if (element is IClickableControl clickable)
  130. {
  131. clickable.Click += Clickable_Click;
  132. }
  133. root.Template = CreateWindowTemplate();
  134. root.ApplyTemplate();
  135. root.Presenter.ApplyTemplate();
  136. HotKeyManager.SetHotKey(element, gesture);
  137. target.ProcessRawEvent(new RawKeyEventArgs(target,
  138. 0,
  139. root,
  140. RawKeyEventType.KeyDown,
  141. Key.A,
  142. RawInputModifiers.Control,
  143. PhysicalKey.A,
  144. "a"));
  145. element.IsEnabled = false;
  146. target.ProcessRawEvent(new RawKeyEventArgs(target,
  147. 0,
  148. root,
  149. RawKeyEventType.KeyDown,
  150. Key.A,
  151. RawInputModifiers.Control,
  152. PhysicalKey.A,
  153. "a"));
  154. Assert.True(clickExecutedCount == 1, $"{factoryName} Execution raised when IsEnabled is false.");
  155. }
  156. }
  157. [Theory]
  158. [MemberData(nameof(ElementsFactory), parameters: true)]
  159. public void HotKeyManager_Should_Not_Invoke_Event_Click_When_Command_Is_Not_Null(string factoryName, Factory factory)
  160. {
  161. using (AvaloniaLocator.EnterScope())
  162. {
  163. var target = new KeyboardDevice();
  164. var clickExecutedCount = 0;
  165. var commandExecutedCount = 0;
  166. AvaloniaLocator.CurrentMutable
  167. .Bind<IWindowingPlatform>().ToConstant(new MockWindowingPlatform());
  168. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  169. void DoExecute(object parameter)
  170. {
  171. commandExecutedCount++;
  172. }
  173. void Clickable_Click(object sender, Interactivity.RoutedEventArgs e)
  174. {
  175. clickExecutedCount++;
  176. }
  177. var root = new Window();
  178. var element = factory(0, DoExecute, root) as InputElement;
  179. if (element is IClickableControl clickable)
  180. {
  181. clickable.Click += Clickable_Click;
  182. }
  183. root.Template = CreateWindowTemplate();
  184. root.ApplyTemplate();
  185. root.Presenter.ApplyTemplate();
  186. HotKeyManager.SetHotKey(element, gesture);
  187. target.ProcessRawEvent(new RawKeyEventArgs(target,
  188. 0,
  189. root,
  190. RawKeyEventType.KeyDown,
  191. Key.A,
  192. RawInputModifiers.Control,
  193. PhysicalKey.A,
  194. "a"));
  195. element.IsEnabled = false;
  196. target.ProcessRawEvent(new RawKeyEventArgs(target,
  197. 0,
  198. root,
  199. RawKeyEventType.KeyDown,
  200. Key.A,
  201. RawInputModifiers.Control,
  202. PhysicalKey.A,
  203. "a"));
  204. Assert.True(commandExecutedCount == 1, $"{factoryName} Execution raised when IsEnabled is false.");
  205. Assert.True(clickExecutedCount == 0, $"{factoryName} Execution raised event Click.");
  206. }
  207. }
  208. public static TheoryData<string, Factory> ElementsFactory(bool withCommand) =>
  209. new TheoryData<string, Factory>()
  210. {
  211. {nameof(Button), withCommand ? MakeButton : MakeButtonWithoutCommand},
  212. {nameof(MenuItem),withCommand ? MakeMenu : MakeMenuWithoutCommand},
  213. };
  214. private static AvaloniaObject MakeMenu(int expectedParameter, Action<object> action, Window root)
  215. {
  216. var menuitem = new MenuItem()
  217. {
  218. Command = new Command(action),
  219. CommandParameter = expectedParameter,
  220. };
  221. var rootMenu = new Menu();
  222. rootMenu.Items.Add(menuitem);
  223. root.Content = rootMenu;
  224. return menuitem;
  225. }
  226. private static AvaloniaObject MakeButton(int expectedParameter, Action<object> action, Window root)
  227. {
  228. var button = new Button()
  229. {
  230. Command = new Command(action),
  231. CommandParameter = expectedParameter,
  232. };
  233. root.Content = button;
  234. return button;
  235. }
  236. private static AvaloniaObject MakeMenuWithoutCommand(int expectedParameter, Action<object> action, Window root)
  237. {
  238. var menuitem = new MenuItem()
  239. {
  240. };
  241. var rootMenu = new Menu();
  242. rootMenu.Items.Add(menuitem);
  243. root.Content = rootMenu;
  244. return menuitem;
  245. }
  246. private static AvaloniaObject MakeButtonWithoutCommand(int expectedParameter, Action<object> action, Window root)
  247. {
  248. var button = new Button()
  249. {
  250. };
  251. root.Content = button;
  252. return button;
  253. }
  254. private static FuncControlTemplate CreateWindowTemplate()
  255. {
  256. return new FuncControlTemplate<Window>((parent, scope) =>
  257. {
  258. return new ContentPresenter
  259. {
  260. Name = "PART_ContentPresenter",
  261. [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
  262. }.RegisterInNameScope(scope);
  263. });
  264. }
  265. class Command : System.Windows.Input.ICommand
  266. {
  267. private readonly Action<object> _execeute;
  268. #pragma warning disable 67 // Event not used
  269. public event EventHandler CanExecuteChanged;
  270. #pragma warning restore 67 // Event not used
  271. public Command(Action<object> execeute)
  272. {
  273. _execeute = execeute;
  274. }
  275. public bool CanExecute(object parameter) => true;
  276. public void Execute(object parameter) => _execeute?.Invoke(parameter);
  277. }
  278. }
  279. }