【问题标题】:MenuItem keyboard shortcuts in 'pure' MVVM?'纯'MVVM中的MenuItem键盘快捷键?
【发布时间】:2011-12-23 15:46:34
【问题描述】:

我在 wpf 中使用的所有菜单/上下文菜单/工具栏都在 ViewModel 代码中声明,如下所示:

MenuService.Add( new MenuItem()
  {
    Header = "DoStuff",
    Command = new relayCommand( DoStuff, () => CanDoStuffExecute() )
    // some more properties like parent item/image/...
  } );

MenuService 提供单个绑定点,它是 MenuItem 的分层列表,并绑定到 xaml 中的实际 Menu 的 ItemsSource。

这很好用,现在我想以同样方便的方式添加键盘快捷键。 理想情况下,MenuItem 将获得System.Windows.Input.KeyGesture 类型的属性,因此我可以简单地编写

Shortcut = new KeyGesture( Key.D, ModifierKeys.Control )

这将导致在拥有菜单的窗口中按 Ctrl+D 时调用项目的命令,并且还会导致在菜单中自动显示“Ctrl+D”。

但是我在这里迷路了:我想通过数据绑定设置 MenuItem.InputBindings 集合,但它是仅获取的。无论如何,我怎样才能将物品放入其中?或者是否有已经支持这样的东西的 MVVM 框架?我在键盘快捷键上发现的大多数问答都是关于通过xaml设置快捷键的,这没有任何帮助。

更新

搜索“relaycommand vs routeduicommand”和“relaycommand keygesture”等确实揭示了足够的信息来提出一个可行的解决方案。肯定还有其他更好的方法,但目前这对我来说是超低优先级并且可以完美地完成工作。我向 MenuItem 类添加了两个属性,如下所示:

//Upon setting a Gesture, the original command is replaced with a RoutedCommand
//since that automatically gives us proper display of the keyboard shortcut.
//The RoutedCommand simply calls back into the original Command.
//It also sets the CommandBinding property, which still has to be added to the
//CommandBindingCollection of either the bound control or one of it ancestors
public InputGesture Gesture
{
  set
  {
    var origCmd = Command;
    if( origCmd == null )
      return;
    var routedCmd = new RoutedCommand( Header,
      typeof( System.Windows.Controls.MenuItem ),
      new InputGestureCollection { value } );
    CommandBinding = new CommandBinding( routedCmd,
      ( sender, args ) => origCmd.Execute( args.Parameter ),
      ( sender, args ) => { args.CanExecute = origCmd.CanExecute( args.Parameter ); } );
    Command = routedCmd;
  }
}

//CommandBinding created when setting Gesture
public CommandBinding CommandBinding { get; private set; }

所以这提供了我最初要求的功能(即在代码中添加键盘快捷键,它们很容易配置等)。剩下的就是注册命令绑定。目前,只需将它们全部添加到 Application.Current.MainWindow.CommandBindings 即可完成。

【问题讨论】:

    标签: c# wpf mvvm menuitem


    【解决方案1】:

    这实际上并不符合“答案”的条件(我显然无法添加评论)-但我建议您正在做的事情不是 WPF 中的预期方法。您正在以 Windows 窗体的方式(以及在许多其他工具包中)执行此操作 - 在代码中定义您的 UX。你已经做到了,但现在你遇到了砖墙:关键手势纯粹是 UX,绝对不能在代码隐藏中指定。外观(作为视图模型的函数)和用户与其交互(执行给定命令的方式)适用于 XAML 定义。

    属性值和命令适用于您的视图模型,因此您可以将此视图模型重用于其他视图,也可以轻松地为其创建单元测试。在视图模型中实现键盘快捷键将如何帮助可测试性?对于在其他视图中使用,有人可能会争辩说,实际的快捷方式可能不适用于新视图,因此这不属于这些视图。当然,您可能还有其他原因 - 但我建议您可以考虑在 XAML 中定义这些。

    -根据您的评论添加-

    您说的很对 - 我见过一些相当大的 WPF UX 项目,它们努力避免任何代码 - 并以不必要的迟钝告终。我尝试使用能产生有效结果的任何方法,并且尽可能简单。

    这是一个简单地创建 MenuItem 的示例 sn-p..

    <Menu x:Name="miMain" DockPanel.Dock="Top">
        <MenuItem Command="{Binding Path=MyGreatCommand}" Header="DoSomething"/>
    

    创建菜单。这里,MyGreatCommand 是一个ICommand,只是视图模型上的一个属性。我通常将其放在DockPanel 中,以处理StatusBar 等。

    分配按键手势..

    <Window.InputBindings>
        <KeyBinding Key="X" Modifiers="ALT" Command="{Binding Path=MyGreatCommand}"/>
    

    但是,既然您提到您已经搜索过答案并且只找到了 XAML - 我假设您已经尝试过这种方法。我使用RoutedUICommands 而不是用户定义的ICommands 来在标题文本中获得漂亮的右对齐键手势,但我还没有找到如何做到这两点。如果您坚持在代码中创建命令和按键手势,您可能必须创建 RoutedUICommands。

    您为什么要在 XAML 之外设置按键手势?

    如果您希望某些菜单项仅在某些状态在您的视图模型中发挥作用时出现,那么您可以将菜单项(可以包含其他菜单项)的 Visibility 属性绑定到 @987654331 @ 或Visible

    【讨论】:

    • 你能提供一些代码示例吗?你的意思是我应该在 xaml 中定义完整的菜单吗?还是只是捷径?我想看看如何.. 我需要一个全局“工具”菜单之类的东西,插件可以在其中随意添加子菜单——这甚至可以在 xaml 中完成吗?还假设它可以,它会有点用 xaml 乱扔代码,不是吗?现在我需要添加一个菜单命令就是如图所示的代码。这是尽可能短和方便的,它工作得很好。顺便说一句,你的砖墙有两个方向:我还必须看到一个大型应用程序,其中没有在代码中定义单个 UX 部分。这是一个神话。
    • 你说得对——我见过一些相当大的 WPF UX 项目,它们非常努力地避免任何代码——结果不必要地迟钝。我尝试使用任何可行且最简单的方法。这是一个示例 sn-p(请原谅缺乏细节 - 我不确定如何在此网站上格式化).. '
    • 好吧,你并没有真正直接回答这个问题,但提到 RoutedUICommands 让我开始了,我现在有一个可行的解决方案/hack,所以无论如何我都会接受。毕竟这个问题已经足够老了,无论如何它都不会再获得太多的意见了..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    • 2018-09-16
    • 2015-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多