Page 1 of 1

Global Hotkeys for WPF Applications - C#

#1 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4462
  • View blog
  • Posts: 7,776
  • Joined: 08-June 10

Posted 26 June 2013 - 02:20 PM

This tutorial is supplemental to my other Global Hotkeys tutorial. That tutorial is for Windows Forms, whereas this is for WPF. However, I will not go into detail on things I've already covered in the previous tutorial, so please read that one as well as this one.

Occasionally you'll write an application that needs to respond to hotkeys, even when your application is not focused. As described in my previous tutorial, there is no manged API for hotkeys. To use hotkeys, you'll need to use functions from the Win32 API.

See the full project and example usages here, on my Github page.

WPF is a little different than Windows Forms. You don't have a WndProc method to override, and you don't have an easily available Window Handle to use. I'll show you how to achieve this in WPF.

Instead of a Form or an IWin32Window, you'll have a System.Windows.Window (which is the base class for WPF Windows). But, we can still get the information we need, using tools defined in the System.Windows.Interop namespace.

You can get the Window Handle from a WPF window this way. Note that the handle will be invalid if the window hasn't loaded yet. To prevent this, only do this during or after the Loaded event.

//window is a WPF Window object
IntPtr hWnd = new WindowInteropHelper(window).Handle;


Using the same Window object (during the same time frame), you can also hook into the Windows message pump, which you'll need to use to be notified of the hotkey windows messages.

//pass your WPF Window object to this method (Window derives from Visual)
private void HookWndProc(Visual window)
{
	var source = PresentationSource.FromVisual(window) as HwndSource;
	if (source == null) throw new HotkeyException("Could not create hWnd source from window.");
	source.AddHook(WndProc);
}

//WndProc method that we added a hook for:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    //handle windows messages here
    return IntPtr.Zero;
}


Now that we have our message pump and our window handle, we can register hotkeys using the functions defined in User32.dll:

public static class User32
{
    [DllImport("user32.dll")]
    internal static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);

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


These two methods are not defined in our code, they're defined in User32.dll. But we can call them by using System.Runtime.InteropServices, using the DllImport attribute.

To actually use these methods, you'd provide the required parameters: a window handle, a unique ID for the hotkey, Modifiers, and a key code.

Side note: the System.Windows.Input.Key enumeration is not the same as the System.Windows.Forms.Keys enumeration. The former does not have the same key codes, so don't use it. You can either add a reference to System.Windows.Forms, or create your own enum. Here's the source you'd need if you choose the second route:

Spoiler


And we'll want a separate modifiers enumeration that matches the documentation:

[Flags]
public enum Modifiers
{
    NoMod = 0x0000,
    Alt = 0x0001,
    Ctrl = 0x0002,
    Shift = 0x0004,
    Win = 0x0008
}


So, an example of registering a hotkey:

bool result = User32.RegisterHotkey(handle, id, (int)Modifiers.Ctrl, (int)Keys.A);


That will return true or false based on whether or not it succeeded.

Now that you've registered a hotkey, you'll need to filter the windows messages to get only the WM_HOTKEY messages:

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if(msg == 0x0312)
	{
		var lpInt = (int)lParam;
		Keys key = (Keys)((lpInt >> 16) & 0xFFFF);
		Modifiers modifier = (Modifiers)(lpInt & 0xFFFF);
	}
    return IntPtr.Zero;
}


In that previous example, we get the key and modifiers from the lParam of the windows message, but only if the message was the WM_HOTKEY message (0x0312). Since you can have more than one hotkey defined for your code, you should check your Keys and Modifiers to determine which hotkey was pressed.

At this point, you have everything you need to start handling hotkeys in your WPF program. I suggest downloading my full project from Github, because I've extracted this functionality into a class with events. There is an example project showing its use.

Thanks for reading, post any questions you have in the comments section.

Is This A Good Question/Topic? 2
  • +

Replies To: Global Hotkeys for WPF Applications - C#

#2 vmv  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 9
  • Joined: 30-July 13

Posted 18 August 2013 - 05:34 AM

Is this code overwriting for example the key mapping of a loaded game ?
I have a functional code like this but is taking control of the game mapped keys.
So i need a better solution.
Thank you,
Was This Post Helpful? 0
  • +
  • -

#3 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4462
  • View blog
  • Posts: 7,776
  • Joined: 08-June 10

Posted 18 August 2013 - 07:10 PM

I honestly don't understand what you're asking.
Was This Post Helpful? 0
  • +
  • -

#4 vmv  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 9
  • Joined: 30-July 13

Posted 19 August 2013 - 02:25 AM

View PostCurtis Rutland, on 18 August 2013 - 07:10 PM, said:

I honestly don't understand what you're asking.


Well, when i register a hot key, i can't use that hot key combination into a game anymore for example. And this is why i need this code...to register a hot key and be able to use it in both ways...application and game.
Was This Post Helpful? 0
  • +
  • -

#5 modi123_1  Icon User is offline

  • Suitor #2
  • member icon



Reputation: 9073
  • View blog
  • Posts: 34,115
  • Joined: 12-June 08

Posted 19 August 2013 - 06:46 AM

Perhaps don't run your app and your game at the same time.
Was This Post Helpful? 0
  • +
  • -

#6 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4462
  • View blog
  • Posts: 7,776
  • Joined: 08-June 10

Posted 19 August 2013 - 08:02 AM

Well, the problem is that the Hotkey message comes from Windows. It's baked into the OS, so you can't really alter the behavior.

What you can do is find if your game is running or not. Then if it is, you can just ignore the fact that they hotkey was pressed.
Was This Post Helpful? 0
  • +
  • -

#7 vmv  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 9
  • Joined: 30-July 13

Posted 19 August 2013 - 09:10 AM

View PostCurtis Rutland, on 19 August 2013 - 08:02 AM, said:

Well, the problem is that the Hotkey message comes from Windows. It's baked into the OS, so you can't really alter the behavior.

What you can do is find if your game is running or not. Then if it is, you can just ignore the fact that they hotkey was pressed.


Wow, it's a good start :)....let's read and see what i am able to do :). Btw, if i need help with this matter, can i ask in this forum for help ? I mean, i already have one working Hotkey code and now with this news i will try to implement this ignore process. I hope to succeed anyway. Thank you,
Was This Post Helpful? 0
  • +
  • -

#8 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4462
  • View blog
  • Posts: 7,776
  • Joined: 08-June 10

Posted 19 August 2013 - 11:37 AM

Yes you can ask for help here.
Was This Post Helpful? 0
  • +
  • -

#9 vmv  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 9
  • Joined: 30-July 13

Posted 19 August 2013 - 11:57 AM

This is a full working solution also for wpf taken from Stackoverflow (user: Eric Ouellet).
How can i add an exception for a process like "game.exe" if i need this exactly for that game ?!
I'm confused. :(/>
Code:
public partial class MainWindow : Window
{
	HotKey _hotKey;
}
public MainWindow()
{
	InitializeComponent();
	_hotKey = new HotKey(Key.F9, KeyModifier.Shift | KeyModifier.Win, OnHotKeyHandler);
}

Usage:

private void OnHotKeyHandler(HotKey hotKey)
{
    MessageBox.Show("test ok...!");
}

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
    }

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


Like i said, it's taking over the game mapped key and i can't use it because i use in game what keys i hook :)/>
Was This Post Helpful? 0
  • +
  • -

#10 modi123_1  Icon User is offline

  • Suitor #2
  • member icon



Reputation: 9073
  • View blog
  • Posts: 34,115
  • Joined: 12-June 08

Posted 19 August 2013 - 11:59 AM

Hopefully this is with in the games terms of use.
Was This Post Helpful? 0
  • +
  • -

#11 vmv  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 9
  • Joined: 30-July 13

Posted 19 August 2013 - 12:07 PM

View Postmodi123_1, on 19 August 2013 - 11:59 AM, said:

Hopefully this is with in the games terms of use.

It's like a special type of keyboard. For example, i miss my little finger at my left hand and it's hard to use in any game CTRL-ALT-SHIFT++ combinations....:). Well, nothing special.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1