如何显示菜单项的工作键盘快捷键?

O. *_*per 19 wpf keyboard-shortcuts key-bindings menuitem

我正在尝试使用具有键盘快捷键的菜单项创建可本地化的WPF菜单栏 - 而不是加速键/助记符(通常显示为可以按下以在菜单已打开时直接选择菜单项的带下划线的字符),但键盘快捷键(通常是Ctrl+的组合another key)在菜单项标题旁边右对齐显示.

我正在为我的应用程序使用MVVM模式,这意味着我尽可能避免在代码隐藏中放置任何代码并让我的视图模型(我分配给DataContext属性)提供我的视图中控件使用的ICommand接口的实现.


作为重现问题的基础,这里有一些应用程序的最小源代码,如下所述:

Window1.xaml

<Window x:Class="MenuShortcutTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MenuShortcutTest" Height="300" Width="300">
    <Menu>
        <MenuItem Header="{Binding MenuHeader}">
            <MenuItem Header="{Binding DoSomethingHeader}" Command="{Binding DoSomething}"/>
        </MenuItem>
    </Menu>
</Window>
Run Code Online (Sandbox Code Playgroud)

Window1.xaml.cs

using System;
using System.Windows;

namespace MenuShortcutTest
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            this.DataContext = new MainViewModel();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

MainViewModel.cs

using System;
using System.Windows;
using System.Windows.Input;

namespace MenuShortcutTest
{
    public class MainViewModel
    {
        public string MenuHeader {
            get {
                // in real code: load this string from localization
                return "Menu";
            }
        }

        public string DoSomethingHeader {
            get {
                // in real code: load this string from localization
                return "Do Something";
            }
        }

        private class DoSomethingCommand : ICommand
        {
            public DoSomethingCommand(MainViewModel owner)
            {
                if (owner == null) {
                    throw new ArgumentNullException("owner");
                }

                this.owner = owner;
            }

            private readonly MainViewModel owner;

            public event EventHandler CanExecuteChanged;

            public void Execute(object parameter)
            {
                // in real code: do something meaningful with the view-model
                MessageBox.Show(owner.GetType().FullName);
            }

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

        private ICommand doSomething;

        public ICommand DoSomething {
            get {
                if (doSomething == null) {
                    doSomething = new DoSomethingCommand(this);
                }

                return doSomething;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

WPF MenuItem有一个InputGestureText特性,但是在做题,如描述这个,这个,这个这个,纯粹是化妆品和有任何作用上有什么快捷键应用程序实际处理.

SO问题,如人指出,该命令应与被链接KeyBindingInputBindings窗口的列表中.虽然它启用了该功能,但它不会自动显示菜单项的快捷方式.Window1.xaml更改如下:

<Window x:Class="MenuShortcutTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MenuShortcutTest" Height="300" Width="300">
    <Window.InputBindings>
        <KeyBinding Key="D" Modifiers="Control" Command="{Binding DoSomething}"/>
    </Window.InputBindings>
    <Menu>
        <MenuItem Header="{Binding MenuHeader}">
            <MenuItem Header="{Binding DoSomethingHeader}" Command="{Binding DoSomething}"/>
        </MenuItem>
    </Menu>
</Window>
Run Code Online (Sandbox Code Playgroud)

我已经尝试手动设置InputGestureText属性,使Window1.xaml看起来像这样:

<Window x:Class="MenuShortcutTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MenuShortcutTest" Height="300" Width="300">
    <Window.InputBindings>
        <KeyBinding Key="D" Modifiers="Control" Command="{Binding DoSomething}"/>
    </Window.InputBindings>
    <Menu>
        <MenuItem Header="{Binding MenuHeader}">
            <MenuItem Header="{Binding DoSomethingHeader}" Command="{Binding DoSomething}" InputGestureText="Ctrl+D"/>
        </MenuItem>
    </Menu>
</Window>
Run Code Online (Sandbox Code Playgroud)

这确实显示了快捷方式,但由于明显的原因,它不是可行的解决方案:

  • 当实际的快捷方式绑定发生更改时,它不会更新,因此即使用户无法配置快捷方式,此解决方案也是一个维护噩梦.
  • 文本需要本地化(例如,Ctrl密钥在某些语言中具有不同的名称),因此如果任何快捷方式发生变化,则所有翻译都需要单独更新.

我特地到创建一个IValueConverter用于绑定InputGestureText属性设置InputBindings窗口的列表(可能有不止一个KeyBindingInputBindings列表,或根本没有,所以没有具体的KeyBinding,我可以绑定到(如果实例KeyBinding甚至使其本身成为一个有约束力的目标)).这在我看来是最理想的解决方案,因为它非常灵活,同时非常干净(它不需要在各个地方过多的声明),但一方面,InputBindingCollection没有实现INotifyCollectionChanged,因此绑定会更换快捷方式时不会更新,另一方面,我没有设法以一种整洁的方式为转换器提供对我的视图模型的引用(它需要访问本地化数据).更重要的是,InputBindings它不是依赖属性,所以我不能将它绑定到属性可以绑定到的公共源(例如视图模型中的输入绑定列表)ItemGestureText.

现在,许多资源(这个问题,那个问题,这个线程,那个问题那个线程指出RoutedCommandRoutedUICommand包含一个内置InputGestures属性,并暗示来自该属性的键绑定会自动显示在菜单项中.

但是,使用这些ICommand实现中的任何一个似乎都会打开一个新的蠕虫,因为它们ExecuteCanExecute方法不是虚拟的,因此不能在子类中重写以填充所需的功能.提供这种方法的唯一方法似乎是CommandBinding在XAML中声明(例如此处此处显示)将命令与事件处理程序连接 - 但是,该事件处理程序将位于代码隐藏中,从而违反了所描述的MVVM体系结构以上.


尽管如此,这意味着将大部分上述结构从内到外转变(这也意味着我需要在如今最早解决当前相对早期的开发阶段中解决问题):

Window1.xaml

<Window x:Class="MenuShortcutTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:MenuShortcutTest"
    Title="MenuShortcutTest" Height="300" Width="300">
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static local:DoSomethingCommand.Instance}" Executed="CommandBinding_Executed"/>
    </Window.CommandBindings>
    <Menu>
        <MenuItem Header="{Binding MenuHeader}">
            <MenuItem Header="{Binding DoSomethingHeader}" Command="{x:Static local:DoSomethingCommand.Instance}"/>
        </MenuItem>
    </Menu>
</Window>
Run Code Online (Sandbox Code Playgroud)

Window1.xaml.cs

using System;
using System.Windows;

namespace MenuShortcutTest
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            this.DataContext = new MainViewModel();
        }

        void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            ((MainViewModel)DataContext).DoSomething();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

MainViewModel.cs

using System;
using System.Windows;
using System.Windows.Input;

namespace MenuShortcutTest
{
    public class MainViewModel
    {
        public string MenuHeader {
            get {
                // in real code: load this string from localization
                return "Menu";
            }
        }

        public string DoSomethingHeader {
            get {
                // in real code: load this string from localization
                return "Do Something";
            }
        }

        public void DoSomething()
        {
            // in real code: do something meaningful with the view-model
            MessageBox.Show(this.GetType().FullName);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

DoSomethingCommand.cs

using System;
using System.Windows.Input;

namespace MenuShortcutTest
{
    public class DoSomethingCommand : RoutedCommand
    {
        public DoSomethingCommand()
        {
            this.InputGestures.Add(new KeyGesture(Key.D, ModifierKeys.Control));
        }

        private static Lazy<DoSomethingCommand> instance = new Lazy<DoSomethingCommand>();

        public static DoSomethingCommand Instance {
            get {
                return instance.Value;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

出于同样的原因(RoutedCommand.Execute也就是非虚拟的),我不知道如何RoutedCommand创建一个RelayCommand类似于在这个问题的答案中使用基础的子类RoutedCommand,所以我不必绕道而行InputBindings窗口-而明确地重新实现从方法ICommandRoutedCommand子类中感觉我可能会打破一些东西.

更重要的是,虽然使用此方法配置的快捷方式自动显示RoutedCommand,但它似乎不会自动本地化.我的理解是添加

System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-de");
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture;
Run Code Online (Sandbox Code Playgroud)

MainWindow构造应确保由框架提供的本地化字符串应该从德国采取CultureInfo-然而,Ctrl不改变Strg,所以,除非我弄错了如何设置CultureInfo的框架提供的字符串,这种方法并不可行无论如何,如果我希望显示的快捷方式正确本地化.

现在,我知道KeyGesture允许我为键盘快捷键指定一个自定义显示字符串,但不仅是RoutedCommand-derived DoSomethingCommand类与我的所有实例(我可以从中接触加载的本地化)脱节,因为这样CommandBinding必须与XAML中的命令链接,相应的DisplayString属性是只读的,因此在运行时加载另一个本地化时无法更改它.

这让我可以选择手动挖掘菜单树(编辑:为了澄清,这里没有代码,因为我没有要求这个,我知道怎么做)以及InputBindings检查哪些命令的窗口列表有任何KeyBinding与它们相关联的实例,以及哪些菜单项链接到这些命令中的任何一个,以便我可以手动设置InputGestureText每个相应的菜单项以反映第一个(或者首选,我希望在这里使用哪个指标)键盘快捷键.每当我认为密钥绑定可能已经改变时,就必须重复这个过程.然而,对于一个基本上是菜单栏GUI的基本功能的东西来说,这似乎是一个非常繁琐的解决方法,因此我确信它不是"正确"的方法.

自动显示配置为适用于WPF MenuItem实例的键盘快捷键的正确方法是什么?

编辑:我发现的所有其他问题都涉及如何使用KeyBinding/ KeyGesture可以实际启用视觉隐含的功能InputGestureText,而不解释如何在描述的情况下自动链接这两个方面.我发现的唯一有点有希望的问题是这个问题,但两年多来没有得到任何答案.

Pav*_*nin 14

我将从警告开始.您可能不仅需要可自定义的热键,还需要菜单本身.所以在InputBindings静态使用之前请三思.
还有一点需要注意InputBindings:它们暗示命令与窗口可视树中的元素相关联.有时您需要没有与任何特定窗口连接的全局热键.

上面说的意思是你可以用另一种方式实现你自己的应用程序范围的手势处理,并正确路由到相应的命令(不要忘记使用对命令的弱引用).

尽管如此,手势感知命令的想法是相同的.

public class CommandWithHotkey : ICommand
{
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        MessageBox.Show("It Worked!");
    }

    public KeyGesture Gesture { get; set; }

    public string GestureText
    {
        get { return Gesture.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); }
    }

    public string Text { get; set; }

    public event EventHandler CanExecuteChanged;

    public CommandWithHotkey()
    {
        Text = "Execute Me";

        Gesture = new KeyGesture(Key.K, ModifierKeys.Control);
    }
}
Run Code Online (Sandbox Code Playgroud)

简单视图模型:

public class ViewModel
{
    public ICommand Command { get; set; }

    public ViewModel()
    {
        Command = new CommandWithHotkey();
    }
}
Run Code Online (Sandbox Code Playgroud)

窗口:

<Window x:Class="CommandsWithHotKeys.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:commandsWithHotKeys="clr-namespace:CommandsWithHotKeys"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <commandsWithHotKeys:ViewModel/>
    </Window.DataContext>
    <Window.InputBindings>
        <KeyBinding Command="{Binding Command}" Key ="{Binding Command.Gesture.Key}" Modifiers="{Binding Command.Gesture.Modifiers}"></KeyBinding>
    </Window.InputBindings>
    <Grid>
        <Menu HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="Auto">
            <MenuItem Header="Test">
                <MenuItem InputGestureText="{Binding Command.GestureText}" Header="{Binding Command.Text}" Command="{Binding Command}">
                </MenuItem>
            </MenuItem>
        </Menu>
    </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

当然,您应该以某种方式从配置加载手势信息,然后使用数据加载init命令.

下一步是像VS中的键盘:Ctrl + K,Ctrl + D,快速搜索给出了这个问题.


归档时间:

查看次数:

11846 次

最近记录:

8 年 前