Click here to Skip to main content
15,880,469 members
Articles / Programming Languages / C++
Article

Extracting Accelerators As Human Readable Text

Rate me:
Please Sign up or sign in to vote.
4.77/5 (18 votes)
24 Aug 2004BSD5 min read 83.2K   848   34   18
This article shows how to extract accelerators and convert them into human readable text, using the language settings for the current user.

Sample Image - getacceltext.png

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:

typedef struct tagACCEL {
    BYTE fVirt;
    WORD key;
    WORD cmd;
} ACCEL, *LPACCEL;

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

int CopyAcceleratorTable(      
    HACCEL  hAccelSrc,
    LPACCEL lpAccelDst,
    int     cAccelEntries
);

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,
    int    nSize
);

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:

// pseudo code - see source code for real code
String s;

if(accel[i].fVirt & FALT)
   s += GetKeyNameText(MapVirtualKey(VK_MENU));
   
if(accel[i].fVirt & FCONTROL) {
   if(s) s += "+";
   s += GetKeyNameText(MapVirtualKey(VK_CONTROL));
}

if(accel[i].fVirt & FSHIFT) {
   if(s) s += "+";
   s += GetKeyNameText(MapVirtualKey(VK_SHIFT));
}
   
if(accel[i].fVirt & FVIRTKEY) {
   scancode = MapVirtualKey(accel[i].key);
   switch(accel[i].key) {
      case VK_INSERT:
      case VK_DELETE:
      case VK_HOME:
      case VK_END:
      case VK_NEXT:  // Page down
      case VK_PRIOR: // Page up
      case VK_LEFT:
      case VK_RIGHT:
      case VK_UP:
      case VK_DOWN:
         scancode |= 0x100; // Add extended bit
   }
   s += GetKeyNameText(scancode);
} else { // ASCII key code
   s += (char)accel[i].key;
}

// s is now Shift+Ctrl+Ins for instance

Source Code

The source code which is attached to this article contains three functions:

  • bool GetAcceleratorTexts(HACCEL hAccel, std::map<UINT, 
    CString>& mapId2AccelText); 
  • bool GetAcceleratorTexts(HINSTANCE hInst, LPCTSTR lpszAccelRes, 
    std::map<UINT, CString>& mapId2AccelText); 
  • bool GetAcceleratorTexts(HINSTANCE hInst, int nId, std::map<UINT, 
    CString>& mapId2AccelText);

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


Written By
Software Developer (Senior)
Sweden Sweden
I make software.

Comments and Discussions

 
QuestionMy vote of 5 Pin
kanalbrummer3-Jan-14 4:13
kanalbrummer3-Jan-14 4:13 
GeneralNice! Pin
David Pritchard26-Nov-08 4:08
David Pritchard26-Nov-08 4:08 
Questiondifferent accelerators in dialog applications? Pin
Ștefan-Mihai MOGA20-Mar-06 22:00
professionalȘtefan-Mihai MOGA20-Mar-06 22:00 
Hello, how can I use different keyboard accelerators in a dialog based application? How can I costomize those for each dialog of the application? Thanks in advance for your answer.

Generalcontainer type Pin
vjedlicka27-Aug-04 1:46
vjedlicka27-Aug-04 1:46 
GeneralRe: container type Pin
Jörgen Sigvardsson27-Aug-04 2:12
Jörgen Sigvardsson27-Aug-04 2:12 
GeneralRe: container type Pin
vjedlicka27-Aug-04 2:48
vjedlicka27-Aug-04 2:48 
GeneralRe: container type Pin
Jörgen Sigvardsson27-Aug-04 3:02
Jörgen Sigvardsson27-Aug-04 3:02 
GeneralRe: container type Pin
vjedlicka27-Aug-04 3:10
vjedlicka27-Aug-04 3:10 
GeneralRe: container type Pin
Anonymous29-Aug-04 20:50
Anonymous29-Aug-04 20:50 
QuestionPublic domain? Pin
Paul Selormey25-Aug-04 10:32
Paul Selormey25-Aug-04 10:32 
AnswerRe: Public domain? Pin
Jörgen Sigvardsson25-Aug-04 12:36
Jörgen Sigvardsson25-Aug-04 12:36 
GeneralRe: Public domain? Pin
Paul Selormey25-Aug-04 14:26
Paul Selormey25-Aug-04 14:26 
GeneralMissing picture Pin
João Paulo Figueira24-Aug-04 1:44
professionalJoão Paulo Figueira24-Aug-04 1:44 
GeneralRe: Missing picture Pin
Jörgen Sigvardsson24-Aug-04 2:13
Jörgen Sigvardsson24-Aug-04 2:13 
GeneralRe: Missing picture Pin
Nemanja Trifunovic24-Aug-04 2:28
Nemanja Trifunovic24-Aug-04 2:28 
GeneralRe: Missing picture Pin
Jörgen Sigvardsson24-Aug-04 8:30
Jörgen Sigvardsson24-Aug-04 8:30 
GeneralRe: Missing picture Pin
Nish Nishant24-Aug-04 19:03
sitebuilderNish Nishant24-Aug-04 19:03 
GeneralRe: Missing picture Pin
Jörgen Sigvardsson24-Aug-04 22:44
Jörgen Sigvardsson24-Aug-04 22:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.