diff --git a/src/Eto.Wpf/Forms/Menu/MenuItemHandler.cs b/src/Eto.Wpf/Forms/Menu/MenuItemHandler.cs index b3eb0c04c..057f0ccee 100644 --- a/src/Eto.Wpf/Forms/Menu/MenuItemHandler.cs +++ b/src/Eto.Wpf/Forms/Menu/MenuItemHandler.cs @@ -70,11 +70,42 @@ public string ToolTip set { Control.ToolTip = value; } } + + class EtoKeyGesture : swi.InputGesture + { + public EtoKeyGesture(MenuItemHandler menuItemHandler, swi.Key key, swi.ModifierKeys modifiers) + { + MenuItemHandler = menuItemHandler; + Key = key; + Modifiers = modifiers; + } + + public MenuItemHandler MenuItemHandler { get; } + + public swi.Key Key { get; } + public swi.ModifierKeys Modifiers { get; } + + public override bool Matches(object targetElement, swi.InputEventArgs inputEventArgs) + { + // only match if enabled, which is different from WPF's default behavior + // this allows specific keys to be used in the application when the menu item is disabled + if (!MenuItemHandler.Enabled) + return false; + if (inputEventArgs is swi.KeyEventArgs args && IsDefinedKey(args.Key)) + { + return ( (int)Key == (int)args.Key ) && ( Modifiers == swi.Keyboard.Modifiers ); + } + return false; + } + + internal static bool IsDefinedKey(swi.Key key) => key >= swi.Key.None && key <= swi.Key.OemClear; + } + public Keys Shortcut { get { - var keyBinding = Control.InputBindings.OfType().FirstOrDefault(); + var keyBinding = Control.InputBindings.OfType().FirstOrDefault(); return keyBinding == null ? Keys.None : keyBinding.Key.ToEtoWithModifier(keyBinding.Modifiers); } set @@ -85,7 +116,8 @@ public Keys Shortcut { var key = value.ToWpfKey(); var modifier = value.ToWpfModifier(); - Control.InputBindings.Add(new swi.KeyBinding { Key = key, Modifiers = modifier, Command = this }); + var gesture = new EtoKeyGesture(this, key, modifier); + Control.InputBindings.Add(new swi.InputBinding(this, gesture)); Control.InputGestureText = value.ToShortcutString(); AddKeyBindings(Control); } @@ -93,14 +125,17 @@ public Keys Shortcut Control.InputGestureText = null; } } - + public bool Enabled { - get { return Control.IsEnabled; } + get => Control.IsEnabled; set { - Control.IsEnabled = value; - OnCanExecuteChanged(EventArgs.Empty); + if (Control.IsEnabled != value) + { + Control.IsEnabled = value; + OnCanExecuteChanged(EventArgs.Empty); + } } } diff --git a/src/Eto.Wpf/Forms/WpfWindow.cs b/src/Eto.Wpf/Forms/WpfWindow.cs index 8cac8b32e..9d942645d 100755 --- a/src/Eto.Wpf/Forms/WpfWindow.cs +++ b/src/Eto.Wpf/Forms/WpfWindow.cs @@ -478,16 +478,6 @@ public void Close() } - void CopyKeyBindings(swc.ItemCollection items) - { - foreach (var item in items.OfType()) - { - Control.InputBindings.AddRange(item.InputBindings); - if (item.HasItems) - CopyKeyBindings(item.Items); - } - } - public MenuBar Menu { get { return menu; } @@ -496,6 +486,7 @@ public MenuBar Menu if (IsAttached) throw new NotSupportedException(); menu = value; + Control.InputBindings.Clear(); if (menu != null) { var handler = (MenuBarHandler)menu.Handler; diff --git a/test/Eto.Test/UnitTests/Forms/MenuItemTests.cs b/test/Eto.Test/UnitTests/Forms/MenuItemTests.cs old mode 100644 new mode 100755 index 808c08e99..a2b00c58d --- a/test/Eto.Test/UnitTests/Forms/MenuItemTests.cs +++ b/test/Eto.Test/UnitTests/Forms/MenuItemTests.cs @@ -105,6 +105,79 @@ public void MenuItemEnabledShouldUpdateCommandIfSpecified() Assert.IsFalse(command.Enabled, "#3.1"); Assert.AreEqual(item.Enabled, command.Enabled, "#3.2"); } + + [TestCase(true, Keys.E, false, true)] // Fails in Gtk, Wpf (shortcut takes precedence over intrinsic behaviour) + [TestCase(false, Keys.E, false, true)] + [TestCase(true, Keys.E | Keys.Control, false, false)] // Fails in Gtk, Mac (menu shortcuts always takes precedence) + [TestCase(true, Keys.E | Keys.Control, true, false)] // Fails in Gtk, Mac (menu shortcuts always takes precedence) + [TestCase(false, Keys.E | Keys.Control, true, false)] + [TestCase(false, Keys.E | Keys.Control, false, false)] + [ManualTest] + public void TextBoxShouldAcceptInputEvenWhenShortcutDefined(bool enabled, Keys key, bool handleKey, bool shouldBeIntrinsic) + { + ControlShouldAcceptInputEvenWhenShortcutDefined(enabled, key, handleKey, shouldBeIntrinsic); + } + + [TestCase(true, Keys.E, false, false)] + [TestCase(false, Keys.E, false, false)] + [TestCase(true, Keys.E, true, false)] + [TestCase(false, Keys.E, true, false)] + [TestCase(true, Keys.E | Keys.Control, false, false)] // Fails in Gtk, Mac (menu shortcuts always takes precedence) + [TestCase(true, Keys.E | Keys.Control, true, false)] // Fails in Gtk, Mac (menu shortcuts always takes precedence) + [TestCase(false, Keys.E | Keys.Control, true, false)] + [TestCase(false, Keys.E | Keys.Control, false, false)] + [ManualTest] + public void DrawableShouldAcceptInputEvenWhenShortcutDefined(bool enabled, Keys key, bool handleKey, bool shouldBeIntrinsic) + { + ControlShouldAcceptInputEvenWhenShortcutDefined(enabled, key, handleKey, shouldBeIntrinsic, d => d.CanFocus = true); + } + void ControlShouldAcceptInputEvenWhenShortcutDefined(bool enabled, Keys key, bool handleKey, bool shouldBeIntrinsic, Action initialize = null) + where T: Control, new() + { + var itemWasClicked = false; + var keyWasPressed = false; + ManualForm($"Press the {key.ToShortcutString()} key", form => + { + var menu = new MenuBar(); + var item = new ButtonMenuItem { Text = "Disabled Item", Enabled = enabled, Shortcut = key }; + item.Click += (sender, e) => + { + itemWasClicked = true; + Log.Write(sender, "Item was clicked"); + }; + menu.Items.Add(new SubMenuItem { Text = "File", Items = { item } }); + form.Menu = menu; + var control = new T { Size = new Size(200, 200) }; + initialize?.Invoke(control); + control.KeyDown += (sender, e) => + { + if (e.KeyData == key) + { + // key was pressed! yay. + Log.Write(sender, "Key was pressed on control"); + keyWasPressed = true; + + if (handleKey) + e.Handled = true; + } + }; + form.Shown += (sender, e) => control.Focus(); + + return control; + }); + + if (!enabled || handleKey || shouldBeIntrinsic) + { + Assert.IsFalse(itemWasClicked, "#1 - ButtonMenuItem was triggered, but should not have been"); + } + else + { + Assert.IsTrue(itemWasClicked, "#1 - ButtonMenuItem was not triggered"); + } + + Assert.IsTrue(keyWasPressed, "#2 - Key was not pressed"); + } + } } \ No newline at end of file