Rash thoughts about .NET, C#, F# and Dynamics NAV.


"Every solution will only lead to new problems."

Friday, 6. November 2009


Observing global hotkeys in C# / F#

Filed under: C#,F# — Steffen Forkmann at 11:08 Uhr

I recently had the problem to register a global hotkey, but my “old” Win32-API calls didn’t work with WPF. I looked around the web and found the “Managed Windows API”, but I didn’t want to add another external dependency to my project, so I extracted the core functions for registering hotkeys and condensed the code to a new version.

As the original “Managed Windows API” is licensed by the GNU Lesser General Public License (LGPL) I want to provide my modifications here.

First we need a Window which can dispatch the window messages to our event handlers:

/// <summary>

/// A Win32 native window that delegates window messages to

/// handlers. So several

/// components can use the same native window to save

/// "USER resources". This class

/// is useful when writing your own components.

/// </summary>

public sealed class EventDispatchingNativeWindow : NativeWindow

{

    private static readonly Object MyLock = new Object();

    private static EventDispatchingNativeWindow _instance;

 

    /// <summary>

    /// Create your own event dispatching window.

    /// </summary>

    public EventDispatchingNativeWindow()

    {

        CreateHandle(new CreateParams());

    }

 

    /// <summary>

    /// A global instance which can be used by components

    /// that do not need their own window.

    /// </summary>

    public static EventDispatchingNativeWindow Instance

    {

        get

        {

            lock (MyLock)

            {

                return

                  _instance ??

                     (_instance =

                       new EventDispatchingNativeWindow());

            }

        }

    }

 

    /// <summary>

    /// Attach your event handlers here.

    /// </summary>

    public event WndProcEventHandler EventHandler;

 

    /// <summary>

    /// Parse messages passed to this window and send

    /// them to the event handlers.

    /// </summary>

    /// <param name="m">A System.Windows.Forms.Message

    /// that is associated with the

    /// current Windows message.</param>

    protected override void WndProc(ref Message m)

    {

        bool handled = false;

        if (EventHandler != null)

            EventHandler(ref m, ref handled);

        if (!handled)

            base.WndProc(ref m);

    }

}

Now we can write our global hotkey handler class:

/// <summary>

/// Specifies a class that creates a global keyboard hotkey.

/// </summary>

public class GlobalHotkey

{

    private static readonly Object MyStaticLock = new Object();

    private static int _hotkeyCounter = 0xA000;

 

    private readonly int _hotkeyIndex;

    private readonly IntPtr _hWnd;

 

    /// <summary>

    /// Initializes a new instance of this class.

    /// </summary>

    /// <param name="keys">The keys.</param>

    /// <param name="ctrl">if <c>true</c> [CTRL].</param>

    /// <param name="alt">if <c>true</c> [alt].</param>

    /// <param name="shift">if <c>true</c> [shift].</param>

    /// <param name="winKey">if <c>true</c> [win key].</param>

    public GlobalHotkey(Keys keys, bool ctrl,

               bool alt, bool shift, bool winKey)

    {

        KeyCode = keys;

        Ctrl = ctrl;

        Alt = alt;

        Shift = shift;

        WindowsKey = winKey;

        EventDispatchingNativeWindow.Instance.EventHandler

                += NwEventHandler;

        lock (MyStaticLock)

        {

            _hotkeyIndex = ++_hotkeyCounter;

        }

        _hWnd = EventDispatchingNativeWindow.Instance.Handle;

        Enable();

    }

 

    /// <summary>

    /// The key code of the hotkey.

    /// </summary>

    public Keys KeyCode { get; private set; }

 

    /// <summary>

    /// Whether the shortcut includes the Control modifier.

    /// </summary>

    public bool Ctrl { get; private set; }

 

    /// <summary>

    /// Whether this shortcut includes the Alt modifier.

    /// </summary>

    public bool Alt { get; private set; }

 

    /// <summary>

    /// Whether this shortcut includes the shift modifier.

    /// </summary>

    public bool Shift { get; private set; }

 

    /// <summary>

    /// Whether this shortcut includes the Windows key modifier.

      /// </summary>

    public bool WindowsKey { get; private set; }

 

    ~GlobalHotkey()

    {

        Disable();

        EventDispatchingNativeWindow.Instance.EventHandler

            -= NwEventHandler;

    }

 

    /// <summary>

    /// Enables the hotkey. When the hotkey is enabled,

    /// pressing it causes a

    /// <c>HotkeyPressed</c> event instead of being

    /// handled by the active application.

    /// </summary>

    private void Enable()

    {

        // register hotkey

        int fsModifiers = 0;

        if (Shift) fsModifiers += ModShift;

        if (Ctrl) fsModifiers += ModControl;

        if (Alt) fsModifiers += ModAlt;

        if (WindowsKey) fsModifiers += ModWin;

        bool success =

           RegisterHotKey(_hWnd, _hotkeyIndex,

                fsModifiers, (int) KeyCode);

 

        if (!success)

            throw new

              Exception(

                "Could not register hotkey (already in use).");

    }

 

    /// <summary>

    /// Disables this instance.

    /// </summary>

    private void Disable()

    {

        // unregister hotkey

        UnregisterHotKey(_hWnd, _hotkeyIndex);

    }

 

    /// <summary>

    /// Occurs when the hotkey is pressed.

    /// </summary>

    public event EventHandler HotkeyPressed;

 

    private void NwEventHandler(ref Message m, ref bool handled)

    {

        if (handled) return;

        if (m.Msg != WmHotkey ||

                m.WParam.ToInt32() != _hotkeyIndex)

            return;

        if (HotkeyPressed != null)

            HotkeyPressed(this, EventArgs.Empty);

        handled = true;

    }

 

    #region PInvoke Declarations

 

    private const int ModAlt = 0x0001;

    private const int ModControl = 0x0002;

    private const int ModShift = 0x0004;

    private const int ModWin = 0x0008;

    private const int WmHotkey = 0x0312;

 

    [DllImport("user32.dll", SetLastError = true)]

    private static extern bool RegisterHotKey(IntPtr hWnd,

          int id, int fsModifiers, int vlc);

 

    [DllImport("user32.dll", SetLastError = true)]

    private static extern bool UnregisterHotKey(IntPtr hWnd,

          int id);

 

    #endregion

}

Now we can easily register a global hotkey or use it as an observable in F#:

/// system-wide keyboard hook

let hotkey = new GlobalHotkey(Keys.Q,true,false,false,false)

let hookObserver =

  hotkey.HotkeyPressed 

    |> Observable.subscribe (fun _ -> printfn "Hotkey pressed")

Tags: , , , ,