An essential feature of any "professional" program is a keyboard customization dialog, where users can assign hotkeys of their choice for all menu commands. It looks like a big job, at least it did to me when I was contemplating adding it to my xplorer². Do I need to have script-ready structure for all the commands? Do I have to add text and description for each one of them? What about translations of the GUI?
Searching in MSDN, CodeProject, and usenet groups for a readymade solution - even paid - turned out fruitless. Searching SourceForge with Krugle (great resource) dug up some promising code snippets, but no complete solution.
As you do, I ended up writing my own keyboard customization class from scratch. The good news is that it makes adding hotkey customization to your WTL application a breeze: just add a header file, a dialog resource, a few lines to the frame creation, and you are done. All the command strings are read from your existing menu structure.
Usability wise, the dialog resembles the hotkey customization dialog of VS6, albeit without command categories. You still get support for more than one accelerator key per command. The class takes care of its own registry persistence, and even allows a reset, where all hotkeys are restored to factory defaults (those defined in the accelerator resource). Finally, the hotkey mnemonics that appear on menu items are automatically updated each time you change the accelerator table.
Command hotkeys are stored in an accelerator table. Most programs have a fixed accelerator table created at design time in a resource editor and loaded when the frame window is created through
LoadAccelerators. In WTL, the handle is stored in a member variable of
m_hAccel. The framework does a good job hiding the use of accelerators. The message pump calls
TranslateMessage on each retrieved message, and if it happens to correspond to an accelerator key, the equivalent
WM_COMMAND is dispatched instead of the keyboard event.
In order to offer adjustable hotkeys, an application must comprise:
- A means to modify the accelerator table on the fly. There is an API called
CreateAcceleratorTable that can achieve that with an array of key/command information (
- A way to read key combinations, gratis
CHotKeyCtrl system control.
- Provide user readable hotkey names, through the
Using these ingredients, changing the hotkeys on the fly involves these steps:
- Obtain a copy of the current accelerator table stored in the frame's
- Use the provided
CKeyAssignDlg to modify the table to taste.
- Destroy the old table and create a new one, setting it to
m_hAccel for immediate effect.
- Update the menu hotkey mnemonics (and toolbar tooltips) to reflect the current keyboard assignments.
That's it! The customized table is stored in a registry key as
REG_BINARY. More details can be found in the source code, which demonstrates a minimal WTL SDI application with full hotkey customization support.
Using the code
The sample code is built using Visual Studio 6, using WTL v7.1 (yes, I am a laggard). I imagine it shouldn't be too hard to build with the latest VS/WTL kit, or even port it to MFC, if you are industrious.
To add keyboard customization to your application, all you need is the main header file keyAssignDlg.h, the dialog
IDD_ACCELEDIT_DLG, and a few error message strings from the resources. To integrate it with your project, you need to add a few member variables and message handlers to your main frame window:
class CMainFrame : public CFrameWindowImpl<CMainFrame>,
public CMessageFilter, public CIdleHandler
LRESULT OnCustomizeKeys(WORD , WORD ,
HWND , BOOL& );
LRESULT OnDestroy(UINT , WPARAM ,
LPARAM , BOOL& );
Run handler has successfully created the main frame, call
CustomAccelerators to load the custom keys from the registry and store them in the helper class
CAccelCombo. We backup the default keys stored in the resources in
m_hAccelDefault in case users reset them from the customization dialog. Finally, during destruction, we save the custom accelerator table back to the registry. See the source code for all the juicy details.
Points of interest
The really neat feat of this dialog class is the way it populates the categories and the commands. It reuses the structure from your main menu resource. It automatically imports all the information from the resources, and you don't have to type command names and descriptions separately. Not rocket science, but very convenient.
Each top level menu becomes a category, and all its commands (and subcommands) are flattened into a list. Command descriptions are taken from the texts that normally appear on the status bar as you traverse the menu system. This ensures a pleasant and straightforward user experience.
Finally, note that the Windows hotkey control used is not perfect. It passes all key combinations that include ENTER, TAB, SPACE, DEL, ESC, or BACKSPACE to
DefWindowProc, which makes these keys unavailable for keyboard shortcuts. I tried using
WM_GETDLGCODE to eat all keys, without much success. At least, the key names returned by
GetKeyNameText are internationalization ready.
- June 2007: Initial release.