Skip to content

Commit

Permalink
Merge pull request #2725 from cwensley/curtis/wpf-disabled-menu-items…
Browse files Browse the repository at this point in the history
…-should-not-stop-keypress

Wpf: Allow intrinsic key behavior of menu shortcut keys when item is disabled
  • Loading branch information
cwensley authored Jan 20, 2025
2 parents 5545a4d + c57bc47 commit e0a0918
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 16 deletions.
47 changes: 41 additions & 6 deletions src/Eto.Wpf/Forms/Menu/MenuItemHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,42 @@ public string ToolTip
set { Control.ToolTip = value; }
}


class EtoKeyGesture : swi.InputGesture
{
public EtoKeyGesture(MenuItemHandler<TControl, TWidget, TCallback> menuItemHandler, swi.Key key, swi.ModifierKeys modifiers)
{
MenuItemHandler = menuItemHandler;
Key = key;
Modifiers = modifiers;
}

public MenuItemHandler<TControl, TWidget, TCallback> 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<swi.KeyBinding>().FirstOrDefault();
var keyBinding = Control.InputBindings.OfType<EtoKeyGesture>().FirstOrDefault();
return keyBinding == null ? Keys.None : keyBinding.Key.ToEtoWithModifier(keyBinding.Modifiers);
}
set
Expand All @@ -85,22 +116,26 @@ 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);
}
else
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);
}
}
}

Expand Down
11 changes: 1 addition & 10 deletions src/Eto.Wpf/Forms/WpfWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -478,16 +478,6 @@ public void Close()

}

void CopyKeyBindings(swc.ItemCollection items)
{
foreach (var item in items.OfType<swc.MenuItem>())
{
Control.InputBindings.AddRange(item.InputBindings);
if (item.HasItems)
CopyKeyBindings(item.Items);
}
}

public MenuBar Menu
{
get { return menu; }
Expand All @@ -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;
Expand Down
73 changes: 73 additions & 0 deletions test/Eto.Test/UnitTests/Forms/MenuItemTests.cs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextBox>(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<Drawable>(enabled, key, handleKey, shouldBeIntrinsic, d => d.CanFocus = true);
}

void ControlShouldAcceptInputEvenWhenShortcutDefined<T>(bool enabled, Keys key, bool handleKey, bool shouldBeIntrinsic, Action<T> 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");
}

}
}

0 comments on commit e0a0918

Please sign in to comment.