定义菜单项快捷键

59

我需要一种简单的方法来为菜单项设置快捷键。

但是这个方法不适用于快捷键,只能使用点击:

<MenuItem Header="Editar">
    <MenuItem Header="Procurar" Name="MenuProcurar"
              InputGestureText="Ctrl+F"
              Click="MenuProcurar_Click">
        <MenuItem.ToolTip>
            <ToolTip>
                Procurar
            </ToolTip>
        </MenuItem.ToolTip>
    </MenuItem>
</MenuItem>

我正在使用 WPF 4.0

7个回答

83

H.B.说得对...我只是想添加更多细节。

从你的MenuItem中删除Click事件,并改为将其与一个Command关联。

1 - 添加/创建你的命令:

<Window.CommandBindings>
     <CommandBinding Command="Open" Executed="OpenCommandBinding_Executed"/>
     <CommandBinding Command="SaveAs" Executed="SaveAsCommandBinding_Executed"/>
</Window.CommandBindings>

这些命令是指以下代码:

private void OpenCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    Open();//Implementation of open file
}
private void SaveAsCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    SaveAs();//Implementation of saveAs
}

2-将命令与所需的键关联:

<Window.InputBindings>
    <KeyBinding Key="O" Modifiers="Control" Command="Open"/>
    <KeyBinding Key="S" Modifiers="Control" Command="SaveAs"/>
</Window.InputBindings>

3 - 最后将命令与菜单项分配(InputGestureText 只是装饰性的文本):

<Menu Name="menu1">
    <MenuItem Header="_File">
        <MenuItem Name="menuOpen" Header="_Open..." Command="Open" InputGestureText="Ctrl+O"/>
        <MenuItem Name="menuSaveAs" Header="_Save as..." Command="SaveAs" InputGestureText="Ctrl+S"/>
    </MenuItem>
</Menu>

这样就可以将多个输入与同一命令关联起来。


2
谢谢。这非常简洁明了,帮了我很多。正是我需要的信息,可以将我的菜单点击处理程序转换为命令处理程序。 - Adarsha
3
更多细节:大多数预定义命令都包括输入绑定,例如 Open 命令。如果您使用此类命令,则无需显式指定 KeyBindingInputGestureText。此外,如果默认文本对您来说可以接受,您可以省略 Header - Martin
我有四个不同的复制菜单项 - 每个都调用一个绑定到CTRL+C的复制命令,但传递不同的参数。如何使上下文菜单中的快捷键文本消失? - Stephen Drew
好的 - 你不能将InputGestureText设置为null,因为它默认为绑定的手势 - 所以我将其设置为一个空格,这样它就不再显示了。 - Stephen Drew
我花了很长时间才发现,如果你想摆脱CTRL,并将其与一个按键(如F1)关联起来,你只需要省略属性Modifiers="Control"即可。 - Andrejs Gasilovs

69

为此,您需要在控件中使用KeyBindings(如果您重新使用RoutedCommands,例如在ApplicationCommands中找到的那些命令,则还需要使用CommandBindings),以使快捷键在相应的控件上工作。

例如:

<Window.CommandBindings>
        <CommandBinding Command="New" Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<Window.InputBindings>
        <KeyBinding Key="N" Modifiers="Control" Command="New"/>
</Window.InputBindings>

对于自定义的RoutedCommands

static class CustomCommands
{
    public static RoutedCommand DoStuff = new RoutedCommand();
}

用法:

<Window
    ...
    xmlns:local="clr-namespace:MyNamespace">
        <Window.CommandBindings>
                <CommandBinding Command="local:CustomCommands.DoStuff" Executed="DoStuff_Executed" />
        </Window.CommandBindings>
        <Window.InputBindings>
                <KeyBinding Key="D" Modifiers="Control" Command="local:CustomCommands.DoStuff"/>
        </Window.InputBindings>
    ...
</Window>

通常使用实现ICommand接口比使用RoutedCommands更方便。您可以编写一个构造函数,该函数接受执行和可执行委托以轻松创建执行不同操作的命令,这种实现通常称为DelegateCommandRelayCommand。这样,您就无需使用CommandBindings


9
我做了这件事。但为了一件简单的事情而花费了太多的工作量。 如果不起作用,那么为什么InputGestureText存在? - Felipe Pessoto
5
没有人说过这件事情会很简单;InputGestureText 就是这样:它只是文本,没有其他作用。从参考资料中可以了解到:"该属性不会将输入手势与菜单项关联起来;它只是向菜单项添加文本。应用程序必须处理用户的输入以执行操作。有关如何将命令与菜单项关联的信息,请参见 Command。" - H.B.
5
在WinForms中以前很简单;我想这就是人们抱怨的原因。 - Roman Starkov
10
过于复杂,而且篇幅太长。 - Jonathan.
11
但为什么呢?面向对象编程并不适用于所有情况,它不是万能的。为什么要把事情搞得比必须的更加复杂?Windows窗体快捷方式很方便:点击菜单项>转到属性面板>选择相应的快捷键>保存>调试=可用!WPF:ICommand抽象层、RoutedCommands、KeyBindings、CommandBindings、DelegateCommand、RelayCommand、Execute、CanExecute、ICommand接口、面向对象这个那个以及其它DEBUG,哦,出了点问题,重试一下。 - Jase
显示剩余8条评论

13

在我看来,只需要在标题中使用下划线 _ 就更加简单了。这将自动创建所需的快捷键。

例如:

<MenuItem Header="_Editar">
<MenuItem Header="_Procurar" Name="MenuProcurar"
          InputGestureText="Ctrl+F"
          Click="MenuProcurar_Click">
    <MenuItem.ToolTip>
        <ToolTip>
            Procurar
        </ToolTip>
    </MenuItem.ToolTip>
</MenuItem>
</MenuItem>

14
这将创建一个热键,而不是快捷方式。像使用Ctrl+F这样的热键直接启动查找命令而无需先浏览菜单是相当普遍的。 - Mike Post

10

我被Windows.Forms和VB 6过度偏见,所以我有点赞同JonathanJase,认为有一个更加直接/程序化的方法来静态地连接事件处理程序,这些处理程序不一定是CommandBindings。 我想这是可以实现的。

一个很好的使用非CommandBinding处理程序的教程,尤其是关于按钮的,可以在这篇MSDN博客文章中找到。 我会提炼并针对MenuItem ...

创建ICommand

首先,创建一个实现了 ICommand 接口的类。当然,你可以把它放在任何地方,甚至是你的 MainWindow.xaml.cs 文件中,如果你想让你的演示代码非常简单。当你想要稍后禁用/启用菜单项时,你可能会想让 CanExecute 更加复杂,但是现在,我们将始终启用我们的菜单项。
public class HelloWorldCommand : ICommand
{
    public void Execute(object parameter)
    {
        MessageBox.Show(@"""Hello, world!"" from " 
            + (parameter ?? "somewhere secret").ToString());
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;
} 

正如教程中指出的那样,您已经可以从任何地方调用此命令,例如以下代码...

var hwc = new HelloWorldCommand();
if (hwc.CanExecute(this))
    hwc.Execute(this);

在窗口中声明您的命令

因此,让我们为HelloWorldCommand添加一种类似于“声明”的方法,以便稍后可以使用它。在您的Window标签内部,将该命令注册为资源:

<Window.Resources>
    <local:HelloWorldCommand x:Key="hwc"/>
</Window.Resources>

现在我们有一个方便的快捷方式来链接到这个“本地命名空间”命令,"hwc",虽然您可以使用任何字符串。我们将在xaml中经常使用它。
连接(和重用!)命令
让我们将我们的MenuItem添加到我们的xaml中。我已经用DockPanel替换了原始的Grid,因为这是最简单的方法(对我来说),可以填充Window的等距小部件,尽管我留下了所有其他UI。
请注意,Command =“{StaticResource hwc}”散布在每个MenuItem声明中。关键是里面的hwc - 记住,这是我们在Window级别设置的HelloWorldCommand的快捷方式。当然,StaticResource只是说要查找Window的资源。我们不绑定任何东西;我们只是使用我们的快捷方式。
<DockPanel LastChildFill="True">
    <Menu DockPanel.Dock="Top">
        <MenuItem Header="_File">
            <MenuItem 
                Header="_Open" 
                Command="{StaticResource hwc}" 
            >
                <MenuItem.CommandParameter>
                    <!-- so you could make this object as complex as you wanted, 
                        like, say, your entire Window. See magic incantation, below. -->
                    <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}" />
                </MenuItem.CommandParameter>

            </MenuItem>

            <MenuItem 
                Header="_Close" 
                Command="{StaticResource hwc}" 
                CommandParameter="Close"
                InputGestureText="Ctrl+G" />

            <MenuItem 
                Header="_Save" 
                Command="{StaticResource hwc}" 
                CommandParameter="Save" />

            <Separator />

            <MenuItem 
                Header="_Quit" 
                Command="{StaticResource hwc}" 
                CommandParameter="Quit" />
        </MenuItem>
</DockPanel>

使用CommandParameters区分事件源

请注意我们对所有内容都使用相同的命令!但是我们如何知道哪个小部件触发了事件呢?为此,您需要使用CommandParameter -- 记住我们的Execute方法的签名:Execute(object parameter)。那个CommandParameter参数就是我们可以用来知道如何处理事件的。尝试运行这个程序并注意MessageBox将使用CommandParameter中的任何内容来让您知道事件的来源。虽然我们正在手动完成所有操作,但这并不太难。

还要注意,您可以使这些对象变得非常复杂。您可以在MenuItem标记中使用属性来定义参数,或者可以使用“真正的”<MenuItem.CommandParameter>标记,例如上面的Open菜单项,来定义一些复杂的东西。在这种情况下,我们传递了整个父Window对象,这是把VB6-ish上下文放入事件处理程序代码的最简单(虽然不是最干净)的方法。

MenuItem添加键盘快捷键(也称为“回答OP”)

现在我们终于可以回答最初的问题了!让我们终于为Close菜单项连接一个键盘快捷键。您会注意到,我们已经声明了一个InputGestureText。仅凭InputGestureText只是装饰性的。如果我们过分挑剔,我们可能会争论创建键盘快捷键的机制根本没有与MenuItem直接、固有的关系!

相反,我们需要在Window级别注册一个Ctrl-G的监听器来捕获按键。因此,在您的窗口标记的顶层插入以下内容(基本上来源于这里):

<Window.InputBindings>
    <KeyBinding Modifiers="Control"
                Key="G"
                Command="{StaticResource hwc}" 
                CommandParameter="window input binding"
    />
</Window.InputBindings>

请注意,您可以通过将CommandParameter标签从自闭合的XML移动到“真正的”打开和关闭KeyBinding标签中,在KeyBinding中放置它。
完成后,请运行您的应用程序并按Ctrl-G。很简单。
我认为这比大多数介绍命令和MenuItems的方式要简单得多,一旦您了解了玩家的情况。

可能的专业提示:

整个CommandBinding的事情让我困惑了一段时间。我相信这只是针对特定命令类型的。也就是说,您不能随意连接任何Command。像这里所吹嘘的(在一个不错的入门教程中!)...

可能不完全明显,但通过使用命令,我们刚刚获得了很多免费的东西:键盘快捷键、项目上的文本和InputGestureText以及WPF根据活动控件及其状态自动启用/禁用项目。在这种情况下,由于未选择任何文本,因此“剪切”和“复制”被禁用,但“粘贴”处于启用状态,因为我的剪贴板不为空!

...有点神奇,不一定好,当您新接触WPF菜单时可能会感到困惑。


1
没有快速评论的踩票使得改进答案变得困难。说出问题所在,我会仔细查看! - ruffin
不知道为什么你被踩了,这是一个高质量的答案。 - MKII

8

您也可以在XAML中声明RoutedUICommand

<Window.Resources>
    <RoutedUICommand x:Key="BuildCmd" Text="Build">
        <RoutedUICommand.InputGestures>
            <KeyGesture>CTRL+SHIFT+B</KeyGesture>
        </RoutedUICommand.InputGestures>
    </RoutedUICommand>      
</Window.Resources>

进行绑定操作

<Window.CommandBindings>
    <CommandBinding Command="{StaticResource BuildCmd}" Executed="BuildCmdExecuted"/>
</Window.CommandBindings>

MenuItem

<MenuItem Command="{StaticResource BuildCmd}"/>

这里讨论了另一个解决方案,请点击此处


1
请注意,据我所知,这是将命令绑定到 MenuItem 并"自动"显示手势的唯一方法。其他技术需要显式设置 MenuItemInputGestureText 属性,但如果手势本身是命令的一部分,则会自动显示在菜单项中,无需任何额外的努力。同样地,设置命令的 Text 属性也是如此。 - Peter Duniho
在我看来,使用RoutedUICommands自动填充菜单项、按钮等的文本和加速键信息是最简单的方法。这对我帮助很大! - Bharat Mallapur
@Peter Duniho,<RoutedUICommand> 不是自动显示手势的唯一方法。只要将输入绑定到菜单项调用的命令,它就会显示。 - Nathan Goings
1
@NathanGoings: “你只需要将输入绑定到命令” - 如果不是RoutedUICommand,那么是哪个命令?我不知道是否有其他支持此功能的内置命令类型。请注意,我并不是说您必须像发布的答案中那样在XAML中声明它,但是您确实必须使用一个RoutedUICommand实例。如果有其他机制,请具体说明。 - Peter Duniho

1

这是PowerShell的解决方案:

  1. 定义您的XAML文件:
<Window x:Class="WpfApp1.Window1"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="My App" Height="620" Width="950" >
    <Window.InputBindings>
        <KeyBinding Key="S" Modifiers="Ctrl"/>
    </Window.InputBindings>
    <Grid x:Name="MainGrid">
    <!--Your GUI is here-->
        <Menu Margin="0">
            <MenuItem Header="_File">
                <MenuItem x:Name="SaveProfile" Header="_Save Profile" InputGestureText="Ctrl+S"/>
            </MenuItem>
        </Menu>
    </Grid>
</Window>
  • 新增类型以创建命令
  • Add-Type @"
    public class DelegateCommand : System.Windows.Input.ICommand
    {
        private System.Action<object> _action;
        public DelegateCommand(System.Action<object> action)
        {
            _action = action;
        }
        public bool CanExecute(object parameter)
        {
            return true;
        }
        public event System.EventHandler CanExecuteChanged = delegate { };
        public void Execute(object parameter)
        {
            _action(parameter);
        }
    }
    "@
    
    1. 创建并分配新的命令到先前定义的KeyBinding。它是第一个key binding,这就是为什么我用[0]来表示它的原因。 注意:在我的情况下,我将主窗口的句柄保存在$hash.Window变量中,你应该在此处放置指向你的主窗口对象的链接,该对象是使用[Windows.Markup.XamlReader]::Load($xamlXmlNodeReader)命令或其他创建窗口的命令创建的。
    $hash.Window.InputBindings[0].Command = New-Object DelegateCommand( { Save-Profile } )
    

    4. 创建你的函数并将其放入命令中。
    function Save-Profile {
        Write-Host "Save Profile"
        # Your logic goes here
    }
    

    感谢Nicholas Wolverson提供有关如何创建命令类型的技巧。


    0

    这对我有效

    <ContextMenu  PreviewKeyUp="ContextMenu_PreviewKeyUp">
        <MenuItem Header="Delete"  Click="DeleteID"   />
    </ContextMenu>
    

    代码后台:

    private void ContextMenu_PreviewKeyUp(object sender, KeyEventArgs e)
    {
        ContextMenu contextMenu = sender as ContextMenu;
        if (e.Key == Key.D)
        {
            //DELETE ID
    
        }
        contextMenu.IsOpen = false;
    }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接