Introduction
A system wide hotkey is a key combination that fires a specific event regardless of which application has the input focus. For example, if you press WIN+E, then a new instance of Explorer is fired up, or if you press CTRL+PRINTSCREEN the currently active window is screen printed to the clipboard.
The Win32 API has a couple of calls which are used to set up and respond to hotkeys - specifically RegisterHotKey
and UnregisterHotKey
which you declare in VB.NET thus:
<DllImport("user32", EntryPoint:="RegisterHotKey", _
SetLastError:=True, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Function RegisterHotkey(ByVal hwnd As IntPtr, _
ByVal Id As Int32, _
<MarshalAs(UnmanagedType.U4)> ByVal fsModifiers As Int32, _
<MarshalAs(UnmanagedType.U4)> ByVal vkey As Int32) As Boolean
End Function
<DllImport("user32", EntryPoint:="UnregisterHotKey", _
SetLastError:=True, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Function UnregisterHotkey(ByVal hwnd As Int32, _
ByVal Id As Int32) As Boolean
End Function
After a hotkey has been registered by the API call RegisterHotkey
, then whenever the key combination specified is pressed, a WM_HOTKEY
Windows message is sent to the window passed to it in the hwnd
parameter.
Specifying the key combination
The key that triggers the event is defined by the two parameters fsModifiers
and vKey
.
fsModifiers
is a value which tells the operating system, which modifier keys are included in the hotkey combination - i.e. Alt, Ctrl, Shift or Win key. This value is made by a combination of the following flags:
Public Enum HotkeyModifierFlags
MOD_ALT = &H1
MOD_CONTROL = &H2
MOD_SHIFT = &H4
MOD_WIN = &H8
End Enum
Thus, for example, if you want a hotkey which is CTRL+ALT+C, then the modifier would be MOD_ALT
+ MOD_CONTROL
.
The vKey
parameter is the virtual key code of the key that goes along with the modifier to define the hot key combination.
Specifying a unique hotkey ID
The id
parameter is used to differentiate between multiple hotkeys that can be received by a given window. To ensure that we are dealing with an unique ID, I use the API call GlobalAddAtom
to return the id
. This is declared thus:
<DllImport("kernel32", EntryPoint:="GlobalAddAtom", _
SetLastError:=True, _
ExactSpelling:=False)> _
Public Function GlobalAddAtom(<MarshalAs(UnmanagedType.LPTStr)> _
ByVal lpString As String) As Int32
End Function
Creating a window to listen for the WM_HOTKEY message
The final parameter is a window handle to listen for the WM_HOTKEY
message. Here there are two options - either pass in the handle of an existing window and subclass it's WndProc
to look out for the message or create a new invisible window specifically to listen for the message.
The second option is achieved by creating a new class that derives from System.Windows.Forms.NativeWindow
thus:
Public Class GlobalHotkeyListener
Inherits NativeWindow
#Region "Private member variables"
Private windowHandle As Integer
Private mwh As ManualResetEvent
#End Region
Public Sub New(ByVal Id As Int32, _
ByVal fsModifiers As Int32, _
ByVal vkey As Int32, _
ByRef wh As ManualResetEvent)
mwh = wh
Dim cp As CreateParams = New CreateParams()
cp.Caption = ""
cp.ClassName = "STATIC"
cp.X = 0
cp.Y = 0
cp.Height = 0
cp.Width = 0
cp.Style = WindowStyleBits.WS_MINIMIZE
cp.ExStyle = WindowStyleExtendedBits.WS_EX_NOACTIVATE
Me.CreateHandle(cp)
Try
If Not RegisterHotkey(MyBase.Handle, _
Id, fsModifiers, vkey) Then
Throw New Win32Exception()
End If
Catch e As Exception
System.Diagnostics.Debug.WriteLine(e.ToString)
End Try
End Sub
And the listening for the WM_HOTKEY
is done thus:
<System.Security.Permissions._
PermissionSetAttribute(System.Security.Permissions_
.SecurityAction.Demand, Name:="FullTrust")> _
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case (m.Msg)
Case WM_HOTKEY
If Not mwh Is Nothing Then
mwh.Set()
End If
End Select
MyBase.WndProc(m)
End Sub
Future improvements
Ideally this control should have a UIEditor
derived class to allow the selection of the key combination.