|
Language:
|
Activate your application with global hotkeys
by Francesco Balena
Use RegisterHotKey Windows API function and subclassing to detect when a hotkey is pressed even if your application doesn't have the focus.
Printer friendly version
|
Some Win32 applications may need to be activated quickly and easily by means of a global hotkey, such as Ctrl+F6 or Windows+Q. Doing this with a Windows Form application isn't easily, though.
There are at least two ways to intercept hotkeys in Windows: by setting up a system-level keyboard hook or by registering a global hotkey. Implementing a system-level keyboard hook isn't for the faint of heart and may require some C++ wizardry; fortunately, you rarely need to install a keyboard hook, unless you want to monitor all keys being pressed by the end users while they are working with other applications.
This leaves us with global hotkeys. Global hotkeys are remarkably simpler than system-level keyboard hooks, even though they still need some labour for a correct implementation. First of all, we need to access some functions in the Windows API:
' Windows API functions and constants
Private Declare Function RegisterHotKey Lib "user32" (ByVal hwnd As IntPtr, ByVal id As Integer, _
ByVal fsModifiers As Integer, ByVal vk As Integer) As Integer
Private Declare Function UnregisterHotKey Lib "user32" (ByVal hwnd As IntPtr, ByVal id As Integer) _
As Integer
Private Declare Function GlobalAddAtom Lib "kernel32" Alias "GlobalAddAtomA" (ByVal lpString As _
String) As Short
Private Declare Function GlobalDeleteAtom Lib "kernel32" (ByVal nAtom As Short) As Short
Private Const MOD_ALT As Integer = 1
Private Const MOD_CONTROL As Integer = 2
Private Const MOD_SHIFT As Integer = 4
Private Const MOD_WIN As Integer = 8
// this code assumes the following using statement
// using System.Runtime.InteropServices;
// Windows API functions and constants
[DllImport("user32", SetLastError=true)]
private static extern int RegisterHotKey (IntPtr hwnd, int id, int fsModifiers, int vk);
[DllImport("user32", SetLastError=true)]
private static extern int UnregisterHotKey (IntPtr hwnd, int id);
[DllImport("kernel32", SetLastError=true)]
private static extern short GlobalAddAtom (string lpString);
[DllImport("kernel32", SetLastError=true)]
private static extern short GlobalDeleteAtom (short nAtom);
private const int MOD_ALT = 1;
private const int MOD_CONTROL = 2;
private const int MOD_SHIFT = 4;
private const int MOD_WIN = 8;
Next, you need to install the hotkey, which you do by calling the RegisterHotKey API function: this function takes a unique id, and MSDN docs explain that you should call GlobalAddAtom to get such an id. Besides, you must store this id in a form variable, because you need it later, to unregister the class and delete the global atom you created:
' the id for the hotkey
Dim hotkeyID As Short
' register a global hot key
Sub RegisterGlobalHotKey(ByVal hotkey As Keys, ByVal modifiers As Integer)
Try
' use the GlobalAddAtom API to get a unique ID (as suggested by MSDN docs)
Dim atomName As String = AppDomain.GetCurrentThreadId.ToString("X8") & Me.Name
hotkeyID = GlobalAddAtom(atomName)
If hotkeyID = 0 Then
Throw New Exception("Unable to generate unique hotkey ID. Error code: " & _
Marshal.GetLastWin32Error().ToString)
End If
' register the hotkey, throw if any error
If RegisterHotKey(Me.Handle, hotkeyID, modifiers, CInt(hotkey)) = 0 Then
Throw New Exception("Unable to register hotkey. Error code: " & _
Marshal.GetLastWin32Error.ToString)
End If
Catch ex As Exception
' clean up if hotkey registration failed
UnregisterGlobalHotKey()
End Try
End Sub
' unregister a global hotkey
Sub UnregisterGlobalHotKey()
If Me.hotkeyID <> 0 Then
UnregisterHotKey(Me.Handle, hotkeyID)
' clean up the atom list
GlobalDeleteAtom(hotkeyID)
hotkeyID = 0
End If
End Sub
// the id for the hotkey
short hotkeyID;
// register a global hot key
void RegisterGlobalHotKey( Keys hotkey, int modifiers)
{
try
{
// use the GlobalAddAtom API to get a unique ID (as suggested by MSDN docs)
string atomName = AppDomain.GetCurrentThreadId().ToString("X8") + this.Name;
hotkeyID = GlobalAddAtom(atomName);
if ( hotkeyID == 0 )
{
throw new Exception("Unable to generate unique hotkey ID. Error code: " +
Marshal.GetLastWin32Error().ToString());
}
// register the hotkey, throw if any error
if ( RegisterHotKey(this.Handle, hotkeyID, modifiers, (int) hotkey) == 0 )
{
throw new Exception("Unable to register hotkey. Error code: " + Marshal.GetLastWin32Error()
.ToString());
}
}
catch ( Exception e )
{
// clean up if hotkey registration failed
UnregisterGlobalHotKey();
}
}
// unregister a global hotkey
void UnregisterGlobalHotKey()
{
if ( this.hotkeyID != 0 )
{
UnregisterHotKey(this.Handle, hotkeyID);
// clean up the atom list
GlobalDeleteAtom(hotkeyID);
hotkeyID = 0;
}
}
You typically call the RegisterGlobalHotKey method from the form's Load event handler and the UnregisterGlobalHotKey method from the form's Closed event handler. It is very important that you don't skip the unregistration step, else you will leak resources. The first argument you pass to RegisterGlobalHotKey is one of the System.Windows.Forms.Keys constants, whereas the second argument is either the MOD_WIN value (the Windows logo key) or a combination of MOD_SHIFT, MOD_CONTROL, and MOD_ALT integer constants.
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
' register the Shift+Ctrl+F6 hot key
RegisterGlobalHotKey(Keys.F6, MOD_SHIFT Or MOD_CONTROL)
End Sub
Private Sub Form1_Closed(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Closed
' unregister the hotkey (NEVER FORGET THIS!)
UnregisterGlobalHotKey()
End Sub
private void Form1_Load(object sender, EventArgs e)
{
// register the Shift+Ctrl+F6 hot key
RegisterGlobalHotKey(Keys.F6, MOD_SHIFT | MOD_CONTROL);
}
private void Form1_Closed(object sender, System.EventArgs e)
{
// unregister the hotkey (NEVER FORGET THIS!)
UnregisterGlobalHotKey();
}
After registering the hotkey, Windows sends your form an WM_HOTKEY message whenever the end user presses the key combination. To trap this message you must override the WndProc protected method and take appropriate action (usually activate the form):
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
' let the base class process the message
MyBase.WndProc(m)
' if this is a WM_HOTKEY message, notify the parent object
Const WM_HOTKEY As Integer = &H312
If m.Msg = WM_HOTKEY Then
' do whatever you wish to do when the hotkey is pressed
' in this example we activate the form and display a messagebox
Me.Activate()
MessageBox.Show("Hotkey has been pressed")
End If
End Sub
protected override void WndProc(ref System.Windows.Forms.Message m)
{
// let the base class process the message
base.WndProc(ref m);
// if this is a WM_HOTKEY message, notify the parent object
const int WM_HOTKEY = 0x312;
if ( m.Msg == WM_HOTKEY )
{
// do whatever you wish to do when the hotkey is pressed
// in this example we activate the form and display a messagebox
this.Activate();
MessageBox.Show("Hotkey has been pressed");
}
}
It takes less than one minute to subscribe to our newsletter. You will receive additional material right in your mailbox. Best of all, it’s free!
|