Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Extracting Accelerators As Human Readable Text

0.00/5 (No votes)
24 Aug 2004 1  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here