Introduction
I wanted to include a Hotkey facility in one of my tray applications - which
I had been putting off for some time, since I didn't want to get dirty with
Win32 API calls etc. When I finally tackled it, it proved to be a lot simpler
than I thought. I also chucked together a little user control that I hope some
of you will find useful. The control allows the user to specify their desired
Hotkey, and has useful properties that will get you Hotkey-ing in no time.
Background
This
article [^] is an alternative way to implement Hotkeys - it's a helper
class which is totally separate to your form. However, it uses the Shortcut
enum
, as opposed to the Keys
enum
(which supports 3 simultaneous
modifiers). Also I'm hoping to show that you can easily add Hotkeys to your form
yourself.
Adding a Hotkey to your form in a 4 simple steps
- Import the Interop namespace, since we have to call a Win32 API function to
register the Hotkey.
using System.Runtime.InteropServices;
- Add these static external methods to your form (or a separate class if you
prefer).
[DllImport("user32.dll")]
public static extern bool RegisterHotKey(IntPtr hWnd,
int id,int fsModifiers,int vlc);
[DllImport("user32.dll")]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
- Listen for the Hotkey windows message, by examining all windows messages
passed to the form. Replace
this.Activate()
with your
desired response to the Hotkey (eg: show the form, if you have a tray
application). protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0312)
this.Activate();
base.WndProc(ref m);
}
- Now register your Hotkey. The first argument is the handle to this form
(window). The second is, as far as I can make out, a code to identify your
Hotkey. The third argument specifies which modifier keys to register (just add
up the numbers in the comments depending on your desired combination). The
fourth argument is the character code of the alphanumeric key (or even other
keys). If you are specifying a letter, it must be uppercase.
FrmStartup.RegisterHotKey(this.Handle,
this.GetType().GetHashCode(), 3, (int)'J');
Note that the RegisterHotKey Win32 API call returns true/false
depending on the success of the call. It will fail if another application has
already registered that Hotkey, or if you register the same Hotkey twice for
your application.
Unregistering a Hotkey
Assuming you followed the steps above, just call:
FrmStartup.UnregisterHotKey(this.Handle, this.GetType().GetHashCode());
No need to specify the key combination, that's what the identifier is for. As
far as I can tell, you don't strictly need to unregister the Hotkey when your
application closes. There was no difference in my testing, but to be safe you
probably should.
The ShortcutInput
user control
This user control provides some useful functionality not just for allowing
the user to specify their desired shortcut key, but also getting the modifier
total and character code (required for registering a Hotkey) from a Keys
enumeration instance. The handy thing about the Keys
enum is that it is essentially just an integer, which can
easily be saved in a config or text file.
Features
- Allows combination of 3 modifier keys (Shift, Control, Alt)
- You can set the minimum number of modifiers you require the use to check,
and then access the
IsValid
property to validate.
- Just set the
Keys
property and the checkboxes and
dropdownlist are automatically selected. Now since the Keys
enum is basically an integer, that just leaves you to store/retrieve that number
for a file and register the Hotkey.
Limitations
- The main limitation is that this control does not allow the user to actually
press their key combination to specify it. This input type is offered by a lot
of applications, and is something I may attempt in the future.
- Character can only be alphanumeric (i.e. no support for multimedia keys etc)
- No Win key support. There are a few reasons for this: 1) The Keys
enum
has an LWin & RWin but they are not modifier keys
and so cannot be combined with other keys. 2) usability: even though most
keyboards now have this key, the labeling is not standardized (for example mine
says "Start"), and you would have to explain to the user what the Win key is.
3) three modifiers are sufficient.
- I coded a pretty rudimentary method to extract the character code from a
Keys
enum
instance. I wouldn't go so far as to call it a
"hack" since it will work fine for alphanumeric keys - which is all I needed for
this control. But theoretically I should be using bit masks etc. to separate the
modifiers and the key character properly.
Using ShortcutInput
user control
Add the user control to your toolbox like you normally would. If you're not
sure how to do this, here is one way: copy the user control source file to your
application, rebuild. Right-click the toolbox, select Add/Remove Items. Browse
to your just-rebuilt assembly, and select it. The user control should then
appear at the bottom of your toolbox.
When you load your form you'll need to register your Hotkey, since it does
not persist after you close your application.
int val = GetHotkeySetting();
Keys k = (Keys) val;
bool success = FrmMain.RegisterHotKey(this.Handle,
this.GetType().GetHashCode(),
ShortcutInput.Win32ModifiersFromKeys(k),
ShortcutInput.CharCodeFromKeys(k));
val
would be the integer you stored in your settings
file. Then you'll notice the two static methods of ShortcutInput
that are called to return the modifiers total
separately from the character code.
Now when the user needs to change their Hotkey, on your settings form, place
the control using the designer as you normally would, then at runtime your form
would have to set the Keys property as follows:
int val = GetSerializedInteger();
Keys k = (Keys) val;
ShortcutInput1.Keys = k;
Setting the Keys property will then select the correct checkboxes and the
dropdownlist.
Then when the user clicks Apply, you'll need to register the new Hotkey based
on the user's selection:
bool success = FrmMain.RegisterHotKey(this.Handle,
this.GetType().GetHashCode(),
ShortcutInput1.Win32Modifiers,
ShortcutInput1.CharCode);
I think the properties accessed here are self-explanatory.
Then I'll just demonstrate using the minimum modifiers validation, so you can
require the user to, say, select at least 2 modifiers:
ShortcutInput1.MinModifiers = 2;
Then check the validity:
bool valid = ShortcutInput1.IsValid;
For completeness, I'll list the remaining 3 minor properties. They return
whether each modifier was selected.
bool one = ShortcutInput1.Shift;
bool two = ShortcutInput1.Control;
bool three = ShortcutInput1.Alt;
Some notes about the Keys
enum
As I mentioned I used a rudimentary solution to get the character codes from
a Keys enum instance. The .NET documentation says: "The four right digits of a
key value contain modifier bits for the SHIFT, CONTROL, and ALT keys". Well
since the ALT value is 262144, and is more than 4 digits, this is a bit
confusing. The conclusion that I've come to (but haven't investigated) is that
the first 2 bytes hold the modifiers, and the last 2 bytes hold the key
character. Can someone confirm whether this is the case?
So what I did was use the ToString()
return value to get
the character code. For example: "A, Alt" = A key, "D1, Shift, Control" = digit
1 key etc. Here are some values of key combinations. The first column is the C#
code, the second is the ToString()
value, and the third is
the integer value.
Keys.A | Keys.Alt
Keys.D1 | Keys.Control | Keys.Shift
Keys.LWin | Keys.Alt
Keys.A | Keys.B
Keys.Z | Keys.LWin
Keys.ControlKey
Keys.BrowserBack
Line 4 shows that you cannot combine character keys. Line 5 shows that the
Windows Key is not a modifier key
Points of Interest
This is my first article on CP. Feedback & comments are welcome.
History
- 21 Jan 2004 - Original article