MenuItem HotKey only works after the menu item has been displayed at least once.
It looks like this is the same issue which was fixed in #290.
If the HotKey property of a MenuItem is set, it will not work until the user has opened the menu which contains that MenuItem.
To reproduce the bug simply add the following changes to the Menu sample in the ControlCatalog.
In MenuItemViewModel.cs
public class MenuItemViewModel
{
public string Header { get; set; }
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
public IList<MenuItemViewModel> Items { get; set; }
// Add the Gesture property.
public KeyGesture Gesture { get; set; }
}
In MenuPage.xaml:
<TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
<Menu Items="{Binding MenuItems}">
<Menu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
<!-- Add a binding for the HotKey property -->
<Setter Property="HotKey" Value="{Binding Gesture}"/>
</Style>
</Menu.Styles>
</Menu>
In MenuPageViewModel.cs
Items = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = OpenCommand },
// Assign a gesture to the Save command.
new MenuItemViewModel
{
Header = "Save",
Command = SaveCommand,
Gesture = new Avalonia.Input.KeyGesture(Avalonia.Input.Key.S, Avalonia.Input.InputModifiers.Control)
},
new MenuItemViewModel { Header = "-" },
// The rest of the items....
With these changes, run the ControlCatalog.Desktop project, and go to the Menu page. Press Ctrl+S and notice that nothing will be printed in the output. Open the menu which contains the Save command, and press Ctrl+S again. This time it will print Save in the output.
I don't think that these hotkeys should work when the menu isn't open (focused). What you want are global hotkeys(global commands).
If that would be the desired behavior, I would expect that the hotkeys would stop working if the menu is closed again. But they keep working even when the menu is closed again.
I think unless you change focus the menu still listens to input gestures. There are plans for a global menu. That should probably always listen to input gestures.
I've ran another test to see what happens if the focus is changed. After opening the menu once it doesn't matter what is focused, the hotkey still works. Even after switching to different pages and while typing inside a TextBox. My guess is that it only stops working if nothing is focused at all, but I didn't find a way to test that in the Control Gallery.
Additionally I've found that if you press the Save command in the menu, and then press Ctrl+S it also doesn't execute the command. It looks like the key up and down event is not consumed by anything despite the MenuItem being focused.
This appears to happen because when using bindings to create the MenuItems, the items are not materialized until the Popup is opened.
I notice that WPF does not have HotKey binding for menu items and you seem to need to do this manually in the containing Window - I think WPF has the same behavior as us here, so this might be the reason for their lack of hotkey handling.
I'll need to have a bit more of a think about potential fixes to this.
@kekekeks is currently refactoring Popups to always dispose of their content when a popup is closed. This is only going to make this worse.
I think the only solution is to remove MenuItem.HotKey and add InputBindings as in WPF.
Instead of InputBindings in might be good to look at the 'updated' concept of KeyboardAccelerator from UWP.
https://docs.microsoft.com/en-us/windows/uwp/design/input/keyboard-accelerators https://docs.microsoft.com/en-us/windows/uwp/design/input/keyboard-interactions
Oh, and then there is gestures for special touch input :)
A workaround is to use KeyBindings on the window, if anyone finds this thread in the future:
<Window.KeyBindings>
<KeyBinding Gesture="Ctrl+O" Command="{Binding OpenCommand}" />
</Window.KeyBindings>
I use the following method, which also seems to work, and which doesn't require duplicating the keyboard shortcut descriptions:
In MainWindow.xaml:
<Menu DockPanel.Dock="Top" Name="MainMenu">
<MenuItem Header="_File">
<MenuItem Header="_Open..."
InputGesture="Ctrl+O"
Name="OpenMenuItem"
Command="{Binding OpenCommand}"/>
</MenuItem>
</Menu>
In MainWindow.xaml.cs:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
protected override void OnLoaded(RoutedEventArgs _) {
// At this point, all bindings are resolved, including menu items' .Command properties.
if (DataContext is MainWindowViewModel vm) {
RegisterHotKeys(MainMenu);
}
}
void RegisterHotKeys(Control control) {
if (control is MenuItem item) {
if (item.InputGesture != null && item.Command != null) {
KeyBindings.Add(new KeyBinding() {
Gesture = item.InputGesture,
Command = item.Command
});
}
}
foreach (Control child in control.GetLogicalChildren()) {
RegisterHotKeys(child);
}
}
}
This solution assumes that the DataContext doesn't change, otherwise you'd have to unregister and re-register the hotkeys. For a main window, this should work, though.
Since you closed #16314 as not planned, might as well close this issue. And maybe remove the HotKey property from MenuItem. There is no point in having it if it only works for the most basic of cases.
@Tokter just to clarify, we closed the other issue in favor of this, just because we don't want to have x duplicates of the more or less same issue open .
We are also affected by this issue. Thanks @seleborg for providing the temporary solution
I solved the problem. First of all, the user wants to use the MenuItem shortcut. Then the Menu must have been displayed in front of the user's eyes. So I thought, I can override a Menu, override the OnAttach method of the Menu, and when the Menu is displayed, iterate over its logical child, pick out the MenuItems with hotkeys and register them on the window. When the Menu is badly removed from the user, i.e. removed from the visual tree, I remove these hotkeys again. Based on this idea, I wrote the following code:
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace CatPaw.Controls;
public class CtMenu : Menu
{
private readonly List<KeyBinding> _kbs = new();
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var window = this.FindAncestorOfType<Window>();
if (window is null)
{
return;
}
foreach (var logicalChild in LogicalChildren.OfType<MenuItem>())
{
SearchKeyBinding(logicalChild);
}
foreach (var keyBinding in _kbs)
{
window.KeyBindings.Add(keyBinding);
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
var window = this.FindAncestorOfType<Window>();
if (window is null)
{
return;
}
foreach (var keyBinding in _kbs)
{
window.KeyBindings.Remove(keyBinding);
}
_kbs.Clear();
}
private void SearchKeyBinding(MenuItem mi)
{
foreach (var logicalChild in mi.GetLogicalChildren().OfType<MenuItem>())
{
SearchKeyBinding(logicalChild);
}
if (mi.Command == null || mi.InputGesture == null)
{
return;
}
_kbs.Add(new KeyBinding()
{
Gesture = mi.InputGesture,
Command = mi.Command
});
}
}
Now I use CtMenu in my application. It works. Hope this can help you.
Just wanted to quickly add that I found that if I opened and immediately closed the Menu (using the Menu instance's Open and Close methods) in my window's Opened event, the alt key did summon the expected menu items, and I also suddenly saw the underlined characters in the menu items. This is what I would expect as a Windows user. This opening and closing doesn't seem to be visible to the user at all.