Best Way to Tackle Global Hotkey Processing in C#

Set global hotkeys using C#

Please note that this code will not trigger events in console application projects. You have to use WinForms project for events to fire.

This is the correct code:

public sealed  class KeyboardHook : IDisposable
{
// Registers a hot key with Windows.
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
// Unregisters the hot key with Windows.
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

/// <summary>
/// Represents the window that is used internally to get the messages.
/// </summary>
private class Window : NativeWindow, IDisposable
{
private static int WM_HOTKEY = 0x0312;

public Window()
{
// create the handle for the window.
this.CreateHandle(new CreateParams());
}

/// <summary>
/// Overridden to get the notifications.
/// </summary>
/// <param name="m"></param>
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

// check if we got a hot key pressed.
if (m.Msg == WM_HOTKEY)
{
// get the keys.
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);

// invoke the event to notify the parent.
if (KeyPressed != null)
KeyPressed(this, new KeyPressedEventArgs(modifier, key));
}
}

public event EventHandler<KeyPressedEventArgs> KeyPressed;

#region IDisposable Members

public void Dispose()
{
this.DestroyHandle();
}

#endregion
}

private Window _window = new Window();
private int _currentId;

public KeyboardHook()
{
// register the event of the inner native window.
_window.KeyPressed += delegate(object sender, KeyPressedEventArgs args)
{
if (KeyPressed != null)
KeyPressed(this, args);
};
}

/// <summary>
/// Registers a hot key in the system.
/// </summary>
/// <param name="modifier">The modifiers that are associated with the hot key.</param>
/// <param name="key">The key itself that is associated with the hot key.</param>
public void RegisterHotKey(ModifierKeys modifier, Keys key)
{
// increment the counter.
_currentId = _currentId + 1;

// register the hot key.
if (!RegisterHotKey(_window.Handle, _currentId, (uint)modifier, (uint)key))
throw new InvalidOperationException("Couldn’t register the hot key.");
}

/// <summary>
/// A hot key has been pressed.
/// </summary>
public event EventHandler<KeyPressedEventArgs> KeyPressed;

#region IDisposable Members

public void Dispose()
{
// unregister all the registered hot keys.
for (int i = _currentId; i > 0; i--)
{
UnregisterHotKey(_window.Handle, i);
}

// dispose the inner native window.
_window.Dispose();
}

#endregion
}

/// <summary>
/// Event Args for the event that is fired after the hot key has been pressed.
/// </summary>
public class KeyPressedEventArgs : EventArgs
{
private ModifierKeys _modifier;
private Keys _key;

internal KeyPressedEventArgs(ModifierKeys modifier, Keys key)
{
_modifier = modifier;
_key = key;
}

public ModifierKeys Modifier
{
get { return _modifier; }
}

public Keys Key
{
get { return _key; }
}
}

/// <summary>
/// The enumeration of possible modifiers.
/// </summary>
[Flags]
public enum ModifierKeys : uint
{
Alt = 1,
Control = 2,
Shift = 4,
Win = 8
}

to use (i had to edit the modifier keys to cast them (modifier)1 (modifier)2 etc

public partial  class Form1 : Form
{
KeyboardHook hook = new KeyboardHook();

public Form1()
{
InitializeComponent();

// register the event that is fired after the key press.
hook.KeyPressed +=
new EventHandler<KeyPressedEventArgs>(hook_KeyPressed);
// register the control + alt + F12 combination as hot key.
hook.RegisterHotKey(ModifierKeys.Control | ModifierKeys.Alt,
Keys.F12);
}

void hook_KeyPressed(object sender, KeyPressedEventArgs e)
{
// show the keys pressed in a label.
label1.Text = e.Modifier.ToString() + " + " + e.Key.ToString();
}
}

How to change back and forth application focus with global hotkey in C#

I have found the solution myself and will explain what worked out for me. I have this code here:

using System.Runtime.InteropServices;

[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();

// I get in to here when the clipboard has changed and I need to find the application that has changed the clipboard

// Get the active/originating application handle
IntPtr originatingHandle = GetForegroundWindow();

// ------------------
// Do "some stuff" - this is not required for the application switching but it will get the application process name for the active/originating application

// Get the process ID from the active application
uint processId = 0;
GetWindowThreadProcessId(originatingHandle, out processId);

// Get the process name from the process ID
string appProcessName = Process.GetProcessById((int)processId).ProcessName;

// End "some stuff"
// ------------------

// Change focus to my application - this code is inside my main form (Form1)
SetForegroundWindow(this.Handle);

// Do some more stuff - whatever is required for my application to do
// ...

// Change focus back to the originating application again
SetForegroundWindow(originatingHandle);

At least the above code works for me.

Global hotkeys in windowless .NET app

Using RegisterHotkey() is boilerplate to detect a hotkey press. I won't repeat it here, you can easily google sample code from the function name.

It does however require a window, no workaround for that. It just doesn't have to be a visible window. By far the simplest way to create an invisible window is to use a Form class, like you do in any Winforms application, and set ShowInTaskbar = False, WindowState = Minimized, FormBorderStyle = FixedToolWindow. Call RegisterHotkey() in your OnLoad() method override, you'll need the Handle property. Easy peasy.

Other ways to define a custom global shortcut key in Windows for a C# program?

Here is a solution from a related Stackoverflow post:

Best way to tackle global hotkey processing in c#?

It links to a blog where you can download a library that does what you are looking for.

c# winforms - is it possible to use global hotkey on multiple applications

Based on Jimi's example:

public YourClass()
{
InitializeComponent();
//const int id = 0; // The id of the hotkey.
// For eaxple hot key F5
RegisterHotKey(Handle, 0, (int)KeyModifier.None, Keys.F5.GetHashCode());
}
protected override void WndProc(ref Message message)
{
base.WndProc(ref message);
if (message.Msg == 0x0312)
{
var id = message.WParam.ToInt32();
if (id == 0)
{
/*
* Write a method hee what you want to do.
*/
// Let the command press the right arraybotton instead.
SendKeys.SendWait("{RIGHT}");
}
}
}

Global keyboard capture in C# application

Stephen Toub wrote a great article on implementing global keyboard hooks in C#:

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class InterceptKeys
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;

public static void Main()
{
_hookID = SetHook(_proc);
Application.Run();
UnhookWindowsHookEx(_hookID);
}

private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}

private delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, IntPtr lParam);

private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
Console.WriteLine((Keys)vkCode);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}

Global hotkey in console application

What you can do is Create a hidden window in your Console application which is used to handle the hotkey notification and raise an event.

The code HERE demonstrates the principal. HERE is an article on handling messages in a Console application, using this you should be able to enhance HotKeyManager to run in a Console Application.

The following update to the HotKeyManager creates a background thread which runs the message loop and handles the windows messages.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleHotKey
{
public static class HotKeyManager
{
public static event EventHandler<HotKeyEventArgs> HotKeyPressed;

public static int RegisterHotKey(Keys key, KeyModifiers modifiers)
{
_windowReadyEvent.WaitOne();
int id = System.Threading.Interlocked.Increment(ref _id);
_wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id, (uint)modifiers, (uint)key);
return id;
}

public static void UnregisterHotKey(int id)
{
_wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
}

delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);

private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
{
RegisterHotKey(hwnd, id, modifiers, key);
}

private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
{
UnregisterHotKey(_hwnd, id);
}

private static void OnHotKeyPressed(HotKeyEventArgs e)
{
if (HotKeyManager.HotKeyPressed != null)
{
HotKeyManager.HotKeyPressed(null, e);
}
}

private static volatile MessageWindow _wnd;
private static volatile IntPtr _hwnd;
private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
static HotKeyManager()
{
Thread messageLoop = new Thread(delegate()
{
Application.Run(new MessageWindow());
});
messageLoop.Name = "MessageLoopThread";
messageLoop.IsBackground = true;
messageLoop.Start();
}

private class MessageWindow : Form
{
public MessageWindow()
{
_wnd = this;
_hwnd = this.Handle;
_windowReadyEvent.Set();
}

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY)
{
HotKeyEventArgs e = new HotKeyEventArgs(m.LParam);
HotKeyManager.OnHotKeyPressed(e);
}

base.WndProc(ref m);
}

protected override void SetVisibleCore(bool value)
{
// Ensure the window never becomes visible
base.SetVisibleCore(false);
}

private const int WM_HOTKEY = 0x312;
}

[DllImport("user32", SetLastError=true)]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

[DllImport("user32", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

private static int _id = 0;
}

public class HotKeyEventArgs : EventArgs
{
public readonly Keys Key;
public readonly KeyModifiers Modifiers;

public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
{
this.Key = key;
this.Modifiers = modifiers;
}

public HotKeyEventArgs(IntPtr hotKeyParam)
{
uint param = (uint)hotKeyParam.ToInt64();
Key = (Keys)((param & 0xffff0000) >> 16);
Modifiers = (KeyModifiers)(param & 0x0000ffff);
}
}

[Flags]
public enum KeyModifiers
{
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8,
NoRepeat = 0x4000
}
}

Here is an example of using HotKeyManager from a Console application

using System;
using System.Windows.Forms;

namespace ConsoleHotKey
{
class Program
{
static void Main(string[] args)
{
HotKeyManager.RegisterHotKey(Keys.A, KeyModifiers.Alt);
HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
Console.ReadLine();
}

static void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
{
Console.WriteLine("Hit me!");
}
}
}


Related Topics



Leave a reply



Submit