'纯'MVVM中的MenuItem键盘快捷键?

sti*_*ijn 5 c# wpf mvvm menuitem

我在wpf中使用的所有菜单/上下文菜单/工具栏都在ViewModel代码中声明,非常类似于:

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

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

这非常有效,现在我想以同样方便的方式添加键盘快捷键.理想情况下,MenuItem将获得类型的属性,System.Windows.Input.KeyGesture所以我可以简单地写

Shortcut = new KeyGesture( Key.D, ModifierKeys.Control )
Run Code Online (Sandbox Code Playgroud)

这会导致在拥有菜单的窗口中按Ctrl + D调用项目的命令,这也会导致在菜单中自动显示"Ctrl + D".

但是我迷失在这里:我想通过数据绑定设置MenuItem.InputBindings集合,但它是get-only.我怎么能把物品放进去呢?或者是否有一个MVVM框架已经支持这样的东西?我在键盘快捷键上找到的大多数q&a都是关于通过xaml设置快捷方式,这没有任何帮助.

更新

搜索'relaycommand vs routeduicommand和'relaycommand keygesture'等确实揭示了足够的信息来提出一个工作虽然hacky解决方案.肯定有其他更好的方法,但目前这对我来说是超低优先级,完美地完成了这项工作.我向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; }
Run Code Online (Sandbox Code Playgroud)

所以这给出了我原来要求的功能(即在代码中添加键盘快捷键,它们很容易配置等).剩下的就是注册命令绑定.目前只需将所有这些内容添加到Application.Current.MainWindow.CommandBindings即可完成此操作.

Jam*_*rst 5

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

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

-已添加,以回应您的评论-

你说得对——我见过一些相当大的 WPF UX 项目,它们努力避免任何代码——结果却变得不必要地迟钝。我尝试只使用产生工作结果的任何方法,并且尽可能简单。

这是一个简单地创建 MenuItem 的示例片段。

<Menu x:Name="miMain" DockPanel.Dock="Top">
    <MenuItem Command="{Binding Path=MyGreatCommand}" Header="DoSomething"/>
Run Code Online (Sandbox Code Playgroud)

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

分配按键手势..

<Window.InputBindings>
    <KeyBinding Key="X" Modifiers="ALT" Command="{Binding Path=MyGreatCommand}"/>
Run Code Online (Sandbox Code Playgroud)

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

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

如果您希望某些菜单项仅在某些状态在您的视图模型中占据主导地位时出现,那么您可以将Visibility菜单项(可以包含其他菜单项)的属性绑定到CollapsedVisible