Introduction

I recently bumped into the problem of drawing accelerators in my owner draw
menus. I will with this brief article present how I solved the problem.

Accelerators and Menus

Accelerators are basically keyboard shortcuts which can be mapped to
different functions in your application. The function an accelerator maps to is
a control ID. The same kind of ID which is assigned to buttons, combo boxes,
list boxes, and also menu items. This way it is possible to make different, but
functionally similar, GUI objects represent the same function.

Since using the menus, and subsequently the mouse, is a bad idea from a
physiological point of view, it's imperative that we give our users the choice
to minimize usage of the mouse. That's why showing the accelerators in menus is
a good thing; it informs the user that there are shortcuts, and that using the
mouse is not always mandatory.

The pinnacle of accelerator usage is to make all key combinations user
definable. That however is out of this article's scope.

Accelerator Tables

The accelerator table is basically a table consisting of three columns as
this structure definition tells us:

fVirt is a bit group containing whether the key combination
includes, Ctrl, Alt or Shift. It also tells whether key is a
virtual key code or an ascii code. The cmd member is the ID which
this accelerator is associated with.

Accelerators normally live in your resource section of your binary. In MFC
applications you rarely see them even. MFC, and WTL as well, lets the
accelerator table and main frame window share the same ID. When the main frame
is created, it automatically loads the accelerator table using the same ID it's
got itself. To load the accelerator yourself, you need to use the Win32
function:

HACCEL LoadAccelerators(HINSTANCE hInstance, LPCTSTR lpTableName)

hInstance is typically the instance handle to your executable,
but it may also be a handle to a DLL which you've loaded dynamically.
lpTableName is the resource name. Since you won't have any names
available, but integer resource IDs in your typical MFC project, you will need
to use the macro MAKEINTRESOURCE(). The return value of the
function is a handle to an accelerator. You can't use the handle in a documented
way but to use the functions Win32 provides.

To get to the table itself, you will have copy its contents into a buffer you
provide. The function

will do just that for you. In order to allocate the buffer needed to hold the
entire table, you must first figure out the row count of the table. This can be
done by calling CopyAcceleratorTable(hAccelSrc, 0, 0). The function
will then return the row count of the table. Then allocate the buffer and call
the function again, but this time with the address of your buffer and its length
in table entries.

There, we now possess the knowledge on how to extract the accelerator data
from the resource.

Virtual Key Codes, Scan Codes, and Names

Keys are identified using either virtual key codes or scan codes. A scan code
is a low level code which identifies the key in such degree that you can tell
exactly where it is on the keyboard. If you press the insert key on the
numeric pad, the scan code will represent that key, and not the insert key just
above your arrow keys. In contrast, virtual key codes, does not always make this
distinction. Because of this fact, some virtual key codes translates to several
scan codes. This fact becomes a small problem when translating the keys into
text.

The reason we're doing this exercise is that we want to translate virtual key
codes contained in the accelerator table into human readable text. We'd also
like the translations to adher to the keyboard language settings of the user.
After browsing the MSDN documentation, you will find that there is no virtual
key code-to-text function to help you in your quest. There is however a scan
code-to-text function:

int GetKeyNameText(
LONG lParam,
LPTSTR lpString,
intnSize
);

This shifts the original problem into a problem of mapping virtual key codes
to scan codes. As pointed out earlier this is a problem. There is not always an
unambigous translation from virtual key codes into scan codes. If you call the
function

UINT MapVirtualKey(
UINT uCode,
UINT uMapType
);

to map the virtual key code for insert into a scan code, and then use
the scan code with GetKeyNameText(), you will get the text
"NUM INS". Clearly, this is not what you'd want in your menus. The "NUM" part
would confuse the user, and it would somewhat of a lie since any insert key
would do just fine.

The problem dates back to the day when AT compatible keyboards were
introduced. The older XT compatible keyboards did not have the keys between the
alphanumeric and numeric keyboard (and only 10 function keys, but that's no
problem for us anyway). If you look closely on your keyboard, you will see the
same key setup on the numeric keyboard, which you have on your "extended" part
of the keyboard. To disambiguate scan codes between these two sets, an
extended bit was added to the scan codes.

The extended bit (28, 256d, 100h) will be exploited for those keys
which are on the extended part of the keyboard. If this bit is used with
GetKeyNameText(), any "NUM"s will be removed from the text, and all
will look just great.

So, to translate an accelerator into a non confusing human readable format,
you'd do something like this:

As you can see it depends on CString, so you'll need either MFC,
ATL or WTL to use this code. With very little work you can make it work with
std::basic_string<> as well. All three functions do the same
thing, they just accept different inputs.

The third argument, mapId2AccelText, will hold the texts when
the function returns successfully. As the name hints, it maps the IDs against
each accelerator text.

License

This article, along with any associated source code and files, is licensed under The BSD License

vjedlicka wrote:Is it necessary to load the accelerators using the LoadAccelerators() function, or can I access the accelerators table that had already been loaded to memory by the main frame?

Yes, you can use the HACCEL member of the frame. IIRC, it's m_hAccel or something like that. Basically, any HACCEL would do. How you acquire it depends entirely on your GUI framework.

vjedlicka wrote:Also, I am afraid the std::map is not the right container here because the cmd member of the ACCEL structure is not unique. There may be multiple keys for one command.

True. I did consider that when I wrote it, but I figured that this scenario will not happen that often. I mean, honestly, why would you want to map two different key strokes to the same function? I think that could potentially be confusing for the user. But, by all means, you can easily swap the std::map to a std::multimap without any major modifications.

I strongly agree with you, it is nonsense to have multiple key strokes for one command. But I noticed that the app wizard in MSVS6.0 does that, it generates 2 key strokes for clipboard commands (i.e. Ctrl+C and Ctrl+VK_INSERT).

Hmm.. tricky. "Legacy" accelerators from the DOS era it seems. This is actually a problem. Well not for extracting the accelerators, but for presenting them in a menu for instance. Screen space is way too expensive to show them both. I suppose one could apply some kind of "tie breaker" algorithm.

A cool way to do the tie breaker would be to choose the key stroke with the smallest spanning tree. Think of a key stroke as a collection of nodes, where each node corresponds to a key. The distance between each node is mapped against the distance between the physical keys. Then find the smallest spanning tree for each graph, and compare these. The graph with the smallest spanning tree wins. Should there still be a tie; pick any combination.

"The system supports shortcut assignments available in earlier versions of Microsoft Windows (ALT+BACKSPACE, SHIFT+INSERT, CTRL+INSERT, SHIFT+DELETE). You should consider supporting them (though not documenting them) to support the transition of users." - http://msdn.microsoft.com/library/en-us/dnwue/html/appxB.asp

I actually use those older forms most of the time... But the "correct" thing to do in this case is to only list the Ctrl+... versions in the menus.

Using the distance might end up using Ctrl+C for copy but Shift+Del and Shift+Ins for cut and paste, which would be bad. :/

Jörgen Sigvardsson wrote:Surely I do retain my copyright even if it's public domain?

No, public domain is copyright/left free.

It will be easier to remove the public domain notice and maintain the copyright. If you wish to really put it into the public domain, then remove the restrictions. For the practice, the public domain mostly carry "Author : XXXX" - to indicate the original author.