“纯”MVVM 中的 MenuItem 键盘快捷键?

2024-04-13

我在 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 将获得 type 的属性System.Windows.Input.KeyGesture所以我可以简单地写

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

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

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

Update

搜索“relaycommand vsrouteduicommand”和“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 即可完成。


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

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

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

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

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

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

这就创建了菜单。这里,MyGreatCommand is an ICommand,并且只是视图模型上的一个属性。我通常将其放在一个DockPanel, 来处理StatusBar, etc.

分配按键手势..

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

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

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

如果您希望某些菜单项仅在某些状态在视图模型中起作用时才出现,那么您可以绑定Visibility菜单项(可以包含其他菜单项)的属性Collapsed or Visible.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

“纯”MVVM 中的 MenuItem 键盘快捷键? 的相关文章

  • 在 C++ 中分割大文件

    我正在尝试编写一个程序 该程序接受一个大文件 任何类型 并将其分成许多较小的 块 我想我已经有了基本的想法 但由于某种原因我无法创建超过 12 kb 的块大小 我知道谷歌等上有一些解决方案 但我更感兴趣的是了解这个限制的根源是什么 然后实际
  • 通信对象 System.ServiceModel.Channels.ServiceChannel 不能用于通信

    通信对象System ServiceModel Channels ServiceChannel 无法用于通信 因为它处于故障状态 这个错误到底是什么意思 我该如何解决它 您收到此错误是因为您让服务器端发生 NET 异常 并且您没有捕获并处理
  • 处理 fanart.tv Web 服务响应 JSON 和 C#

    我正在尝试使用 fanart tv Webservice API 但有几个问题 我正在使用 Json Net Newtonsoft Json 并通过其他 Web 服务将 JSON 响应直接反序列化为 C 对象 这里的问题是元素名称正在更改
  • 获取从属性构造函数内部应用到哪个属性的成员?

    我有一个自定义属性 在自定义属性的构造函数内 我想将属性的属性值设置为属性所应用到的属性的类型 是否有某种方式可以访问该属性所应用到的成员从我的属性类内部 可以从 NET 4 5 using CallerMemberName Somethi
  • 为什么 BOOST_FOREACH 不完全等同于手工编码的?

    From 增强文档 http www boost org doc libs 1 48 0 doc html foreach html foreach introduction what is literal boost foreach li
  • 如何用 kevent() 替换 select() 以获得更高的性能?

    来自Kqueue 维基百科页面 http en wikipedia org wiki Kqueue Kqueue 在内核和用户空间之间提供高效的输入和输出事件管道 因此 可以修改事件过滤器以及接收待处理事件 同时每次主事件循环迭代仅使用对
  • Xamarin Android:获取内存中的所有进程

    有没有办法读取所有进程 而不仅仅是正在运行的进程 如果我对 Android 的理解正确的话 一次只有一个进程在运行 其他所有进程都被冻结 后台进程被忽略 您可以使用以下代码片段获取当前正在运行的所有 Android 应用程序进程 Activ
  • C++派生模板类继承自模板基类,无法调用基类构造函数[重复]

    这个问题在这里已经有答案了 我试图从基类 模板 继承 派生类也是模板 它们具有相同的类型 T 我收到编译错误 非法成员初始化 Base 不是基类或成员 为什么 如何调用基类构造函数 include
  • 通过不同 DLL 或 EXE 中的指针或引用访问 STL 对象时发生访问冲突

    我在使用旧版 VC6 时遇到以下问题 我只是无法切换到现代编译器 因为我正在处理遗留代码库 http support microsoft com kb 172396 http support microsoft com kb 172396
  • C# 创建数组的数组

    我正在尝试创建一个将使用重复数据的数组数组 如下所示 int list1 new int 4 1 2 3 4 int list2 new int 4 5 6 7 8 int list3 new int 4 1 3 2 1 int list4
  • 用于从字符串安全转换的辅助函数

    回到 VB6 我编写了一些函数 让我在编码时无需关心字符串的 null 和 数字的 null 和 0 等之间的区别 编码时 没有什么比添加特殊情况更能降低我的工作效率了用于处理可能导致一些不相关错误的数据的代码 9999 10000 如果我
  • 在 C 中复制两个相邻字节的最快方法是什么?

    好吧 让我们从最明显的解决方案开始 memcpy Ptr const char a b 2 调用库函数的开销相当大 编译器有时不会优化它 我不会依赖编译器优化 但即使 GCC 很聪明 如果我将程序移植到带有垃圾编译器的更奇特的平台上 我也不
  • 通过等待任务或访问其 Exception 属性都没有观察到任务的异常

    这些是我的任务 我应该如何修改它们以防止出现此错误 我检查了其他类似的线程 但我正在使用等待并继续 那么这个错误是怎么发生的呢 通过等待任务或访问其 Exception 属性都没有观察到任务的异常 结果 未观察到的异常被终结器线程重新抛出
  • Silverlight Datagrid:在对列进行排序时突出显示整个列

    我的 Silverlight 应用程序中有一个 DataGrid 我想在对该列进行排序时突出显示整个列 它在概念上与上一个问题类似 Silverlight DataGrid 突出显示整列 https stackoverflow com qu
  • 32位PPC rlwinm指令

    我在理解上有点困难rlwinmPPC 汇编指令 旋转左字立即然后与掩码 我正在尝试反转函数的这一部分 rlwinm r3 r3 0 28 28 我已经知道什么了r3 is r3在本例中是一个 4 字节整数 但我不确定这条指令到底是什么rlw
  • 如何检测 C# 中该字典键是否存在?

    我正在使用 Exchange Web 服务托管 API 和联系人数据 我有以下代码 即功能性的 但并不理想 foreach Contact c in contactList string openItemUrl https service
  • 无法使用 Ninject 将依赖项注入到从 Angular 服务调用的 ASP.NET Web API 控制器中

    我将 Ninject 与 ASP NET MVC 4 一起使用 我正在使用存储库 并希望进行构造函数注入以将存储库传递给其中一个控制器 这是实现 StatTracker 接口的上下文对象 EntityFramework public cla
  • 跨项目/dll 访问 Xaml 中的资源

    是否可以从另一个项目引用存储在 ResourceDictionary 构建操作 资源 中的 Xaml 资源 我想将资产合并到主项目的资源字典中或单独访问它们 例如 项目 MyResources 包含一个名为 Assets 的文件夹 其中有一
  • boost::program_options:带有固定和可变标记的参数?

    是否可以在 boost program options 中使用此类参数 program p1 123 p2 234 p3 345 p12 678 即 是否可以使用第一个标记指定参数名称 例如 p 后跟一个数字 是动态的吗 我想避免这种情况
  • 如何确定母版页中正在显示哪个子页?

    我正在母版页上编写代码 我需要知道正在显示哪个子 内容 页面 我怎样才能以编程方式做到这一点 我用这个 string pageName this ContentPlaceHolder1 Page GetType FullName 它以 AS

随机推荐