如何使用 WPF 和 .NET 3.5 注册全局热键以说出 CTRL+SHIFT+(LETTER)?

Posted

技术标签:

【中文标题】如何使用 WPF 和 .NET 3.5 注册全局热键以说出 CTRL+SHIFT+(LETTER)?【英文标题】:How can I register a global hot key to say CTRL+SHIFT+(LETTER) using WPF and .NET 3.5? 【发布时间】:2010-09-08 02:46:16 【问题描述】:

我正在使用 WPF 在 C# 中构建一个应用程序。如何绑定一些键?

另外,我怎样才能绑定到Windows key?

【问题讨论】:

来自 Win32 RegisterHotKey function 文档:“涉及 WINDOWS 键的键盘快捷键保留供操作系统使用。” @IanKemp :在操作系统使用它们之前,您可以在您的应用程序中使用它们。是的,这就是为什么 MS 给 OneNote 提供了 Win+N 和 Win+Shift+N ;-) 【参考方案1】:

这是一个完整的工作解决方案,希望对您有所帮助...

用法:

_hotKey = new HotKey(Key.F9, KeyModifier.Shift | KeyModifier.Win, OnHotKeyHandler);

...

private void OnHotKeyHandler(HotKey hotKey)

    SystemHelper.SetScreenSaverRunning();

类:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Mime;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;

namespace UnManaged

    public class HotKey : IDisposable
    
        private static Dictionary<int, HotKey> _dictHotKeyToCalBackProc;

        [DllImport("user32.dll")]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);

        [DllImport("user32.dll")]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        public const int WmHotKey = 0x0312;

        private bool _disposed = false;

        public Key Key  get; private set; 
        public KeyModifier KeyModifiers  get; private set; 
        public Action<HotKey> Action  get; private set; 
        public int Id  get; set; 

        // ******************************************************************
        public HotKey(Key k, KeyModifier keyModifiers, Action<HotKey> action, bool register = true)
        
            Key = k;
            KeyModifiers = keyModifiers;
            Action = action;
            if (register)
            
                Register();
            
        

        // ******************************************************************
        public bool Register()
        
            int virtualKeyCode = KeyInterop.VirtualKeyFromKey(Key);
            Id = virtualKeyCode + ((int)KeyModifiers * 0x10000);
            bool result = RegisterHotKey(IntPtr.Zero, Id, (UInt32)KeyModifiers, (UInt32)virtualKeyCode);

            if (_dictHotKeyToCalBackProc == null)
            
                _dictHotKeyToCalBackProc = new Dictionary<int, HotKey>();
                ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
            

            _dictHotKeyToCalBackProc.Add(Id, this);

            Debug.Print(result.ToString() + ", " + Id + ", " + virtualKeyCode);
            return result;
        

        // ******************************************************************
        public void Unregister()
        
            HotKey hotKey;
            if (_dictHotKeyToCalBackProc.TryGetValue(Id, out hotKey))
            
                UnregisterHotKey(IntPtr.Zero, Id);
            
        

        // ******************************************************************
        private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
        
            if (!handled)
            
                if (msg.message == WmHotKey)
                
                    HotKey hotKey;

                    if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey))
                    
                        if (hotKey.Action != null)
                        
                            hotKey.Action.Invoke(hotKey);
                        
                        handled = true;
                    
                
            
        

        // ******************************************************************
        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        

        // ******************************************************************
        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be _disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be _disposed.
        protected virtual void Dispose(bool disposing)
        
            // Check to see if Dispose has already been called.
            if (!this._disposed)
            
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                
                    // Dispose managed resources.
                    Unregister();
                

                // Note disposing has been done.
                _disposed = true;
            
        
    

    // ******************************************************************
    [Flags]
    public enum KeyModifier
    
        None = 0x0000,
        Alt = 0x0001,
        Ctrl = 0x0002,
        NoRepeat = 0x4000,
        Shift = 0x0004,
        Win = 0x0008
    

    // ******************************************************************

【讨论】:

我知道已经有一段时间了,但是当我注册我的 PrintScreen 按钮时,实际的“PrintScreen”方法停止工作。 “handled = true”也不起作用。有什么建议吗? @ErwinOkken,我不确定是否理解正确。 HotKey 对你的会话是全局的(~对你的机器来说是全局的)。如果您注册“PrintScreen”,那么当您按下它时,应该在您的程序中处理该键。关闭程序应该让“PrintScreen”正常运行(在运行程序之前)。你的意思是你不能在你的程序中处理(注册)“PrintScreen”? 对不起,如果我不清楚。 PrintScreen 功能不应该失去它的原始功能:将图像存储在剪贴板中。发生这种情况时或之后,我需要钩住它。 @ErwinOkken,我不知道。您可以查看:msdn.microsoft.com/en-ca/library/windows/desktop/… 和 msdn.microsoft.com/en-ca/library/windows/desktop/…。我找不到信息,我总是抓住钥匙。抱歉,我不知道。 这应该是真正的答案,因为它提供了一个真正的全局热键。在您的应用程序中使用全局热键是否是一个好主意还有待商榷。【参考方案2】:

我不确定您在这里所说的“全局”是什么意思,但在这里(我假设您的意思是应用程序级别的命令,例如,Save AllCtrl + Shift + S 从任何地方触发。)

您可以找到您选择的全局UIElement,例如,顶层窗口是您需要此绑定的所有控件的父级。由于 WPF 事件的“冒泡”,子元素上的事件将一直冒泡到控件树的根。

现在,首先你需要

    使用 InputBinding 像这样将 Key-Combo 与命令绑定 然后您可以通过CommandBinding 将命令连接到您的处理程序(例如,由SaveAll 调用的代码)。

对于 Windows 键,您使用正确的 Key 枚举成员、Key.LWinKey.RWin

public WindowMain()

   InitializeComponent();

   // Bind Key
   var ib = new InputBinding(
       MyAppCommands.SaveAll,
       new KeyGesture(Key.S, ModifierKeys.Shift | ModifierKeys.Control));
   this.InputBindings.Add(ib);

   // Bind handler
   var cb = new CommandBinding( MyAppCommands.SaveAll);
   cb.Executed += new ExecutedRoutedEventHandler( HandlerThatSavesEverthing );

   this.CommandBindings.Add (cb );


private void HandlerThatSavesEverthing (object obSender, ExecutedRoutedEventArgs e)

  // Do the Save All thing here.

【讨论】:

查看这里以获得更多 MVVM 风格的方式***.com/questions/19697106/create-key-binding-in-wpf 它并不是真正的全局(应用程序级别)。如果您从 MainWindow 打开新窗口并在那里按热键,它将不起作用。 从 Windows 的角度来看,这也不是“全局”的。如果您的应用不在焦点上,这将不起作用。 遗憾的是,这不是一个全局热键,您需要让您的应用成为焦点才能使其正常工作...【参考方案3】:

注册操作系统级别的快捷方式几乎不是一件好事:用户不希望您弄乱他们的操作系统。

也就是说,在 WPF 中有一种更简单且用户友好的方式来执行此操作,如果您可以使用仅在应用程序中工作的热键(即,只要您的 WPF 应用程序具有焦点):

在 App.xaml.cs 中:

protected override void OnStartup(StartupEventArgs e)

   EventManager.RegisterClassHandler(typeof(Window), Window.PreviewKeyUpEvent, new KeyEventHandler(OnWindowKeyUp));


private void OnWindowKeyUp(object source, KeyEventArgs e))

   //Do whatever you like with e.Key and Keyboard.Modifiers

就这么简单

【讨论】:

当您关闭应用程序时它会自动删除,否则您需要在应用程序的生命周期内取消注册。 +1 为简单起见并指出操作系统级别的快捷方式不好。同意。 我也喜欢它,想知道是否有问题...要访问特殊键,例如 control 或 shift,可以使用 if (e.Key == Key.U && e.KeyboardDevice.IsKeyDown (Key.LeftCtrl)) IMO 这应该是公认的答案,因为它非常优雅。【参考方案4】:

如果您要混合使用 Win32 和 WPF,我是这样做的:

using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows.Media;
using System.Threading;
using System.Windows;
using System.Windows.Input;

namespace GlobalKeyboardHook

    public class KeyboardHandler : IDisposable
    

        public const int WM_HOTKEY = 0x0312;
        public const int VIRTUALKEYCODE_FOR_CAPS_LOCK = 0x14;

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        private readonly Window _mainWindow;
        WindowInteropHelper _host;

        public KeyboardHandler(Window mainWindow)
        
            _mainWindow = mainWindow;
            _host = new WindowInteropHelper(_mainWindow);

            SetupHotKey(_host.Handle);
            ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage;
        

        void ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, ref bool handled)
        
            if (msg.message == WM_HOTKEY)
            
                //Handle hot key kere
            
        

        private void SetupHotKey(IntPtr handle)
        
            RegisterHotKey(handle, GetType().GetHashCode(), 0, VIRTUALKEYCODE_FOR_CAPS_LOCK);
        

        public void Dispose()
        
            UnregisterHotKey(_host.Handle, GetType().GetHashCode());
        
    

您可以在此处获取要注册的热键的虚拟键代码:http://msdn.microsoft.com/en-us/library/ms927178.aspx

可能有更好的方法,但这是我目前所得到的。

干杯!

【讨论】:

如何从这段代码中产生内存泄漏?它的 IDisposable ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage; 这行应该从 UI Thread 调用,否则收不到消息。 RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc) 函数也是如此【参考方案5】:

这类似于已经给出的答案,但我觉得它更清晰:

using System;
using System.Windows.Forms;

namespace GlobalHotkeyExampleForm

    public partial class ExampleForm : Form
    
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        enum KeyModifier
        
            None = 0,
            Alt = 1,
            Control = 2,
            Shift = 4,
            WinKey = 8
        

        public ExampleForm()
        
            InitializeComponent();

            int id = 0;     // The id of the hotkey. 
            RegisterHotKey(this.Handle, id, (int)KeyModifier.Shift, Keys.A.GetHashCode());       // Register Shift + A as global hotkey. 
        

        protected override void WndProc(ref Message m)
        
            base.WndProc(ref m);

            if (m.Msg == 0x0312)
            
                /* Note that the three lines below are not needed if you only want to register one hotkey.
                 * The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */

                Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);                  // The key of the hotkey that was pressed.
                KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF);       // The modifier of the hotkey that was pressed.
                int id = m.WParam.ToInt32();                                        // The id of the hotkey that was pressed.


                MessageBox.Show("Hotkey has been pressed!");
                // do something
            
        

        private void ExampleForm_FormClosing(object sender, FormClosingEventArgs e)
        
            UnregisterHotKey(this.Handle, 0);       // Unregister hotkey with id 0 before closing the form. You might want to call this more than once with different id values if you are planning to register more than one hotkey.
        
    

我在fluxbytes.com找到了它。

【讨论】:

【参考方案6】:

我不确定 WPF,但这可能会有所帮助。我使用 RegisterHotKey (user32) 中描述的解决方案(当然根据我的需要进行了修改)用于 C# Windows 窗体应用程序,以在 Windows 中分配一个 CTRL-KEY 组合以显示一个 C# 窗体,它工作得很好(即使在 Windows Vista 上)。希望对您有所帮助,祝您好运!

【讨论】:

似乎根本无法使用 Windows 键。嗯,它支持 Windows 键,但任何热键都是为 Windows 本身保留的。【参考方案7】:

我在 codeproject.com 上找到了 Global Hotkeys in WPF 项目,它为我完成了这项工作。它是相对较新的,不需要对 System.Windows.Forms 的引用,并且即使“您的”应用程序不是活动窗口,也可以“全局”地对按下的热键做出反应。

【讨论】:

【参考方案8】:

Baboon 的解决方案效果最好,因为您可能有多个窗口。我确实对其进行了调整,因此它使用 PreviewKeyDownEvent 而不是 PreviewKeyUpEvent 来处理击键中的重复。

我建议不要在操作系统级别注册,除非您正在编写诸如截图工具或录音应用程序之类的东西,因为它可以让您在窗口未聚焦时访问功能。

【讨论】:

【参考方案9】:

虽然 RegisterHotKey 有时正是您想要的,但在大多数情况下,您可能不想使用系统范围的热键。我最终使用了如下代码:

using System.Windows;
using System.Windows.Interop;

namespace WpfApp

    public partial class MainWindow : Window
    
        const int WM_KEYUP = 0x0101;

        const int VK_RETURN = 0x0D;
        const int VK_LEFT = 0x25;  
      
        public MainWindow()
        
            this.InitializeComponent();

            ComponentDispatcher.ThreadPreprocessMessage += 
                ComponentDispatcher_ThreadPreprocessMessage;
        

        void ComponentDispatcher_ThreadPreprocessMessage(
            ref MSG msg, ref bool handled)
        
            if (msg.message == WM_KEYUP)
            
                if ((int)msg.wParam == VK_RETURN)
                    MessageBox.Show("RETURN was pressed");
                
                if ((int)msg.wParam == VK_LEFT)
                    MessageBox.Show("LEFT was pressed");
            
        
    

【讨论】:

【参考方案10】:

使用 NHotKey 包,您可以使您的热键全局化:

https://github.com/thomaslevesque/NHotkey https://thomaslevesque.com/2014/02/05/wpf-declare-global-hotkeys-in-xaml-with-nhotkey/(如果链接断开,请使用 web.archive.org)

简而言之,对于 XAML,你需要做的就是替换

<KeyBinding Gesture="Ctrl+Alt+Add" Command="Binding IncrementCommand" />

通过

<KeyBinding Gesture="Ctrl+Alt+Add" Command="Binding IncrementCommand"
            HotkeyManager.RegisterGlobalHotkey="True" />

【讨论】:

【参考方案11】:

John 建议的RegisterHotKey() 可以工作 - 唯一的问题是它需要 HWND(使用 PresentationSource.FromVisual(),并将结果转换为 HwndSource)。

但是,您还需要回复 WM_HOTKEY 消息 - 我不确定是否有办法访问 WPF 窗口的 WndProc(可以在 Windows 窗体窗口中完成) )。

【讨论】:

以上是关于如何使用 WPF 和 .NET 3.5 注册全局热键以说出 CTRL+SHIFT+(LETTER)?的主要内容,如果未能解决你的问题,请参考以下文章

为 Win7 和 Win 8 操作系统开发 WPF .NET 3.5 应用程序

有没有办法用 .Net 3.5 在 WPF 中模拟 UseLayoutRounding

将 VB.NET 2.0 Winform 迁移到 3.5 WPF

如何开始使用 WCF/WPF?

如何使用 mvc 6、asp.net 5 注册全局过滤器

将 npgsql 配置为与 .NET 3.5 和 EF 一起使用