How can I register a global hot key to say CTRL+SHIFT+(LETTER) using WPF and .NET 3.5?

69,623

Solution 1

I'm not sure of what you mean by "global" here, but here it goes (I'm assuming you mean a command at the application level, for example, Save All that can be triggered from anywhere by Ctrl + Shift + S.)

You find the global UIElement of your choice, for example, the top level window which is the parent of all the controls where you need this binding. Due to "bubbling" of WPF events, events at child elements will bubble all the way up to the root of the control tree.

Now, first you need

  1. to bind the Key-Combo with a Command using an InputBinding like this
  2. you can then hookup the command to your handler (e.g. code that gets called by SaveAll) via a CommandBinding.

For the Windows Key, you use the right Key enumerated member, Key.LWin or Key.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.
}

Solution 2

This is a full working solution, hope it helps...

Usage:

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

...

private void OnHotKeyHandler(HotKey hotKey)
{
    SystemHelper.SetScreenSaverRunning();
}

Class:

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
    }

    // ******************************************************************
}

Solution 3

Registering OS level shortcuts is hardly ever a good thing: users don't want you to mess with their OS.

That said, there is a much simpler and user friendly way of doing this in WPF, if you're ok with the hotkey working within the application only (i.e as long as your WPF app has the focus):

In 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
}

It's that simple

Solution 4

If you're going to mix Win32 and WPF, here's how I did it:

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());
        }
    }
}

You can get the virtual-key code for the hotkey you want to register here: http://msdn.microsoft.com/en-us/library/ms927178.aspx

There may be a better way, but this is what I've got so far.

Cheers!

Solution 5

This is similar to the answers already given, but I find it a bit cleaner:

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.
        }
    }
}

I've found it on fluxbytes.com.

Share:
69,623
w-ll
Author by

w-ll

Updated on July 28, 2022

Comments

  • w-ll
    w-ll almost 2 years

    I'm building an application in C# using WPF. How can I bind to some keys?

    Also, how can I bind to the Windows key?

  • erodewald
    erodewald about 12 years
    Doesn't seem to work with the Windows key at all. Well, it supports the Windows key, but any hotkeys are reserved for Windows itself.
  • Louis Kottmann
    Louis Kottmann about 11 years
    It gets removed automatically when you close the app, otherwise yes you need to unregister it within the lifetime of the app.
  • Joel
    Joel about 11 years
    +1 for simplicity and for pointing out that OS level shortcuts are bad. Agreed.
  • anhoppe
    anhoppe almost 9 years
    I like it, too, wonder if there is a catch... To access special keys like control or shift one can use if (e.Key == Key.U && e.KeyboardDevice.IsKeyDown(Key.LeftCtrl))
  • anhoppe
    anhoppe almost 9 years
  • Dork
    Dork over 8 years
    it's not really global (application level). If you open new window from you MainWindow and press hotkey there it wouldn't work.
  • user99999991
    user99999991 over 8 years
    Also this isn't "global" from a Windows perspective. If you app is not in focus, this does not work.
  • user99999991
    user99999991 over 8 years
    How do you produce a memory leak from this code? Its IDisposable
  • Yusuf Tarık Günaydın
    Yusuf Tarık Günaydın over 8 years
    ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage; This line should be called from UI Thread, otherwise you don't receive messages.
  • Yusuf Tarık Günaydın
    Yusuf Tarık Günaydın over 8 years
    This is also the case for the RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc) function
  • Douglas Gaskell
    Douglas Gaskell over 8 years
    Sadly this is not a global hotkey, you need to have your app in focus for this to work...
  • Erwin Okken
    Erwin Okken almost 8 years
    I know it's been a while, but when I register my PrintScreen button, the actual "PrintScreen" method stopped working. "handled = true" didn't work either. Any suggestions?
  • Eric Ouellet
    Eric Ouellet almost 8 years
    @ErwinOkken, I'm not sure to understand properly. A HotKey is global to you session (~global to your machine). If you register "PrintScreen", then when you press it, that key should be handled in your program. Closing your program should let "PrintScreen" run as ~normal (before you run your program). Do you mean that you are not able to handle (register) "PrintScreen" in your program?
  • Erwin Okken
    Erwin Okken almost 8 years
    Sorry if I was unclear. The PrintScreen function should not lose it's original feature: storing the image in the Clipboard. When or after that happens, I need to hook it.
  • Eric Ouellet
    Eric Ouellet almost 8 years
    @ErwinOkken, I don't know. You can look at: msdn.microsoft.com/en-ca/library/windows/desktop/… and msdn.microsoft.com/en-ca/library/windows/desktop/…. I can't find the information and I always grabbed the key. Sorry, I don't know.
  • Kohanz
    Kohanz about 7 years
    This should be the real answer, as it provides a truly global hot key. Whether using a global hot key in your application is a good idea or not is up for debate.
  • Eric Ouellet
    Eric Ouellet almost 7 years
    @ErwinOkken, A lot too late (perhaps it would solve your problem)... But in "ComponentDispatcherThreadFilterMessage" you could not set "handled" to true, then the default behavior should occur also. My suggested way to do it would be to redefine a new filter with this different behavior and a new method "Register" as "RegisterWithDefaultBehavior" and manually call this method instead of let the constructor do it for you.
  • vishwas kumar
    vishwas kumar about 6 years
    IMO this should be the accepted answer because of how elegant it is.
  • Josh Carvin
    Josh Carvin over 2 years
    How can I determine what Reference I am missing for this? I'm getting namespace errors on Key and MSG