Some weeks ago I was trying to make parts of WebCopy's UI a little bit simpler via the expedient of hiding some of the more advanced (and consequently less used) options. And to do this, I created a basic toggle panel control. This worked rather nicely, and while I was writing it I also thought I'd write a short article on adding keyboard support to WinForm controls - controls that are mouse only are a particular annoyance of mine.
A demonstration control
Below is an fairly simple (but functional) button control that works - as long as you're a mouse user. The rest of the article will discuss how to extend the control to more thoroughly support keyboard users, and you what I describe below in your own controls.
About mnemonic characters
I'm fairly sure most developers would know about mnemonic characters / keyboard accelerators, but I'll quickly outline regardless. When attached to a UI element, the mnemonic character tells users what key (usually combined with Alt) to press in order to activate it. Windows shows the mnemonic character with an underline, and this is known as a keyboard cue.
For example, File would mean press Alt+F.
Specifying the keyboard accelerator
In Windows programming, you generally use the
& character to
denote the mnemonic in a string. So for example,
d character is the mnemonic. If you actually wanted to
& character, then you'd just double them up, e.g.
Hello && Goodbye.
While the underlying Win32 API uses the
& character, and most
other platforms such as classic Visual Basic or Windows Forms do
the same, WPF uses the
_ character instead. Which pretty much
sums up all of my knowledge of WPF in that one little fact.
Painting keyboard cues
If you use
TextRenderer.DrawText to render text in your
controls (which produces better output than
Graphics.DrawString) then by default it will render keyboard
Older versions of Windows used to always render these cues. However, at some point (with Window 2000 if I remember correctly) Microsoft changed the rules so that applications would only render cues after the user had first pressed the Alt character. In practice, this means you need to check to see if cues should be rendered and act accordingly. There used to be an option to specify if they should always be shown or not, but that seems to have disappeared with the march towards dumbing the OS down to mobile-esque levels.
The first order of business then is to update our
method to include or exclude keyboard cues as necessary.
TextRenderer.DrawText is a managed wrapper around the
DrawTextEx Win32 API, and most of the members of
TextFormatFlags map to various
DT_* constants. (Except for
NoPadding... I really don't know why
TextRenderer adds left
and right padding by default but it's really annoying - I always
NoPadding (when I'm not directly calling GDI via p/invoke)
As I noted the default behaviour is to draw the cues, so we
need to detect when cues should not be displayed and instruct
our paint code to skip them. To determine whether or not to
display keyboard cues, we can check the
property of the
Control class. To stop
painting the underline, we use the
So we can update our
PaintText method accordingly
Now our button will now hide and show accelerators based on how the end user is working.
If for some reason you want to use
you can use something similar to the below - just set the
HotkeyPrefix property of a
StringFormat object to be
HotkeyPrefix.Hide. Note that the
StringFormat object doesn't show prefixes, in a nice
As the above animation is just a GIF file, there's no audio - but when I ran that demo, pressing Alt+D triggered a beep sound as there was nothing on the form that could handle the accelerator.
Painting focus cues
Focus cues are highlights that show which element has the keyboard focus. Traditionally Windows would draw a dotted outline around the text of an element that performs a single action (such as a button or checkbox), or draws an item using both a different background and foreground colours for an element that has multiple items (such as a listbox or a menu). Normally (for single action controls at least) focus cues only appear after the Tab key has been pressed, memory fails me as to whether this has always been the case or if Windows use to always show a focus cue.
You can use the
Focused property of a
Control to determine
if it currently has keyboard focus and the
property to see if the focus state should be rendered.
After that, the simplest way of drawing a focus rectangle would
be to use the
ControlPaint.DrawFocusRectangle. However, this
draws using fixed colours. Old-school focus rectangles inverted
the pixels by drawing with a dotted XOR pen, meaning you could
erase the focus rectangle by simply drawing it again - this was
great for rubber banding (or dancing ants if you prefer). If you
want that type of effect then you can use the
Notice in the demo above how focus cues and keyboard cues are independent from each other.
So, about those accelerators
Now that we've covered painting our control to show focus /
keyboard cues as appropriate, it's time to actually handle
accelerators. Once again, the
Control class has everything we
need built right into it.
To start with, we override the
ProcessMnemonic method. This
method is automatically called by .NET when a user presses an
Alt key combination and it is up to your component to
determine if it should process it or not. If the component can't
handle the accelerator, then it should return
false. If it
can, then it should perform the action and return
method includes a
char argument that contains the accelerator
key (e.g. just the character code, not the alt modifier).
So how do you know if your component can handle it? Luckily the
Control class offers a static
IsMnemonic method that takes a
char and a
string as arguments. It will return
true if the
source string contains a mnemonic matching the passed character.
Note that it expects the
& character is used to identify the
mnemonic. I assume WPF has a matching version of this method,
but I don't know where.
We can now implement the accelerator handling quite simply using the following snippet
We check to make sure the control can be focused in addition to
checking if our control has a match for the incoming mnemonic,
and if both are true then we set focus to the control and raise
Click event. If you don't need (or want) to set focus to
the control, then you can skip the
CanFocus check and
Bonus Points: Other Keys
Some controls accept other keyboard conventions. For example, a button accepts the Enter or Space keys to click the button (the former acting as an accelerator, the latter acting as though the mouse were being pressed and released), combo boxes accept F4 to display drop downs and so on. If your control mimics any standard controls, it's always worthwhile adding support for these conventions too. And don't forget about focus!
For example, in the sample button, I modify
OnMouseDown to set
focus to the control if it isn't already set
I also add overrides for
OnKeyUp to mimic the
button being pushed and then released when the user presses and
releases the space bar
However, I'm not adding anything to handle the enter key. This
is because I don't need to - in this example, the
control implements the
IButtonControl interface and so it's
handled for me without any special actions. For non-button
controls, I would need to explicitly handle enter key presses if
- 2016-06-03 - First published
- 2020-11-21 - Updated formatting
Like what you're reading? Perhaps you like to buy us a coffee?