Accelerators Manager


CPropertyPage version

Download sample project and source files

I wanted to give to my applications an accelerator editor as we can find in the VC++ (Menu Tools/Customize, Keyboard tab). Basically, the VC++ dialog allows the user to specify any accelerator for any available VC++ command. It checks the existence of overriden keys, handles locked accelerators, and each user can have his own set of accelerators. The code was tested using Microsoft Visual C++ 5.0sp3, under Windows 95 OSR2 and Windows NT4 sp3.

Description

CAcceleratorManager is an MFC based class that handles completely the keyboard customization. It provides also the edition of the declared and added accelerators (with a CDialog or a CPropertyPage). The user can type directly the combination of keystrokes in an edit field and adds it to the selected command. The assign, remove and reset functions are available. The programmer specifiy a type for each accelerator: locked or user. A locked accelerator (displayed gray in the edition dialog) cannot be removed by user (basically Ctrl+O in the VC++, for example). A user accelerator can be redefined using the edition dialogs.

Each configuration is saved under the HKEY_CURRENT_USER registry key, so each user can have his own accelerators. Only the user accelerators are saved, and when the CAcceleratorManager retrieves the accelerators, it erases all the previously defined user accelerators. If the program makes a call to the member function CreateDefaultTable() before the user accelerator are replaced, then the 'Reset All' option will be enabled.

Programmer/User usage

The programmer creates the CAcceleratorManager, connects it to a CWnd (most likely the main frame window) and creates the entries. He sets the defaults accels with their state (locked or user) and asks the manager to record the default map. A call to Load() allows the user to retrieve his owns accels. The programmer must also give a text for each command, for edition purposes. He can choose to edit the accelerator either with a dialog or a property page. The modification functions like SetAccel(), DeleteAccel() or DeleteEntry() can be called at any time. The modifications have to be validated by a call to UpdateWndTable().

From the user standpoint, when the edition is requested, a dialog box or a property page will be displayed, depending on the programmer choice. The user selects a command string, sets the focus on the CEdit, and types his combination of keys. Then a click on the button 'Assign' will check the existence, and adds the accel to the records. The use of 'Remove' and 'Reset All' is trivial... If the user quits with 'Cancel', the modifications are lost and the original table is preserved.

Basic use

First, create an instance of CAcceleratorManager (basically in the CWinApp), connect it to a CWnd object and add news accelerators (with SetAccel()). You can also associate existing resource accelerators to a 'command string' (with AddCommandAccel()) or create empty 'command strings' (with CreateEntry()). Keep in mind that all those functions create entries in the CAcceleratorManager object, not in the CWnd accelerator table. So you MUST call the UpdateWndTable() function to create the ACCEL array and update the CWnd accelerators. After that, a call to CreateDefaultTable() makes a copy of the actual state of the CAcceleratorManager accelerators in the 'default table'. In the sample, those actions are in the CWinApp::InitInstance().

Another important thing to know is that the call to UpdateWndTable() will 'erase' the resource accelerators loaded by Windows. So you must connect ALL the defaults resource accelerators to 'command strings' with AddCommandAccel(), before updating the CAcceleratorManager table. A possible evolution would be to add resources string with the same ID than the resources accelerators, or to create the 'command string' from the menu. It would be be easy to implement and would avoid a long set of calls to AddCommandAccel (see in future version).


//Connection to the frame
m_AccelManager.Connect(pMainFrame);

// Modifications followed by UpdateWndTable().
m_AccelManager.SetAccel(FSHIFT | FVIRTKEY, ID_TEST_ACCEL, WORD(_T('A')), _T("Test accelerators"), true);

// Create an accel command from the Windows resource
m_AccelManager.AddCommandAccel(ID_FILE_NEW, _T("FileNew"));
m_AccelManager.AddCommandAccel(ID_FILE_OPEN, _T("FileOpen"));
// (repeat this for all the commands you want to hold)

// Create an empty entry, it appears as a command key in the dialogs.
m_AccelManager.CreateEntry(ID_APP_ABOUT, _T("About"));

// Update the wnd's ACCEL table, based on the datas contained in the m_AccelManager.
// VERY IMPORTANT AFTER CHANGES
m_AccelManager.UpdateWndTable();

// In order to have the 'Reset All' btn enabled.
m_AccelManager.CreateDefaultTable();

// We can choice any place in the registry, the user accels will
// be save in the 'Keyboard' value, at the specified key.
CString strKey = _T("Software\\");
strKey += m_pszRegistryKey;
strKey += _T("\\");
strKey += m_pszAppName;
strKey += _T("\\Settings");

// Load the precedents users accels, it will erase/update the defaults user's.
m_AccelManager.Load(HKEY_CURRENT_USER, strKey);

Accelerators edition

To edit the accelerators, create a second instance of CAcceleratorManager not connected to a CWnd, and copy the data from the main CAcceleratorManager with the overriden operator =. If you want to use the dialog version, create an instance of CAccelMapDlg with the temporary accelerator manager, and call DoModal(). If the return is IDOK, update the main manager data from the temporary data (with operator =) and call UpdateWndTable() to remap the windows accelerators. The use of the property page version is identical, with a CAccelMapPage object. It is possible at any time to change, delete or add entries (or accelerators) keys with the SetAccel, DeleteAccel, DeleteEntry and CreateEntry functions.


CDialog version

//////////////////////////////////////////////////////////////////////////////
// Accels edit by CDialog
CAcceleratorManager tmpAccelManager;
tmpAccelManager = m_AccelManager;
CAccelMapDlg dlg(&tmpAccelManager);
if (dlg.DoModal() == IDOK) {
	m_AccelManager = tmpAccelManager;
	m_AccelManager.UpdateWndTable();
}


//////////////////////////////////////////////////////////////////////////////
// Accels edit by CPropertyPage
CAcceleratorManager tmpAccelManager;
tmpAccelManager = m_AccelManager;
CAccelMapPage page(&tmpAccelManager);
CPropertySheet sheet;
sheet.AddPage(&page);
if (sheet.DoModal() == IDOK) {
	m_AccelManager = tmpAccelManager;
	m_AccelManager.UpdateWndTable();
}


//////////////////////////////////////////////////////////////////////////////
// Adding/Modification of an accel for the About command
m_AccelManager.SetAccel(FCONTROL | FVIRTKEY, ID_APP_ABOUT, WORD(_T('G')), _T("About"));
m_AccelManager.UpdateWndTable();

CAcceleratorManager API

  • void Connect(CFrameWnd* pWnd, bool bAutoSave = true) : connects the manager with the pWnd, so all changes will be made in this window's accelerators table. If bAutoSave is set to true, the Write() function is called in the destructor.
  • bool GetRegKey(..) / SetRegKey(..) : functions to access the registry key where the user's accelerator will be save or read.
  • bool Load() and bool Load(HKEY hRegKey, LPCTSTR szRegKey) : loads the user's accelerators, and erases the defaults.
  • bool Write() : saves the user's accelerators in the registry
  • bool Default() : erases the user's accelerators, and reloads the default table.
  • bool CreateDefaultTable() : To be called after the default accelerators have been defined. This function saves them in a table, allowing to restore the default accelerators at any time.
  • bool SetAccel(BYTE cVirt, WORD wIDCommand, WORD wKey, LPCTSTR szCommand, bool bLocked) : creates a new accelerator, associated with the szCommand string. szCommand contains the text as it will appear in the edit dialog.
  • bool AddCommandAccel(WORD wIDCommand, LPCTSTR szCommand, bool bLocked) : associates a command message to the szCommand string, and loads the existing accelerator.
  • bool CreateEntry(WORD wIDCommand, LPCTSTR szCommand) : creates a association between the command message and the 'command string'. No accelerator is defined for this command. The user can define them using the edition dialog.
  • bool DeleteEntry(LPCTSTR szCommand) and bool DeleteEntry(WORD wIDCommand) : deletes the command, along with the associated accelerators.
  • bool DeleteAccel(BYTE cVirt, WORD wIDCommand, WORD wKey) : deletes an accelerator, not the corresponding entry. (=> Remove a given accelerator from a command. The command remains defined).
  • bool UpdateWndTable() : replaces the content of the Windows accelerator table with the accelerators currently set in the CAcceleratorManager object. You must call this function to update the window accelerators, after edition or setting.
  • bool GetStringFromACCEL(ACCEL* pACCEL, CString& szAccel) : creates a reading string from an ACCEL structure. Used for menus.
  • bool GetStringFromACCEL(BYTE cVirt, WORD wIDCommand, CString& szAccel) : creates a reading string from a key and a modifier.

 

Implementation and usage

The core of the CAcceleratorManager implementation resides in two CMaps objects. The first associates each 'command string' (for example 'File Open') with a CommandID (IDM_FILE_OPEN), and the second associates the same key (CommandID) with a CCmdAccelOb object. For each ID, we can have several accelerators, so the CCmdAccelOb contains a list of CAccelsOb. These objects have the same definition as the WIN32 ACCEL structure (key, modifier, Command ID). After modification/edition, the CAcceleratorManager creates a new table from the CAccelsObs and updates the pWnd's window accelerator table.

To use this manager, you must add the IDD_EDIT_ACCELMAP dialog in your resources, and add the following files to your project:

  • AcceleratorManager.h, AcceleratorManager.cpp, AccelMgrReg.cpp
  • AccelDlgHelper.h, AccelDlgHelper.cpp
  • AccelMapDlg.h, AccelMapDlg.cpp
  • AccelMapPage.h, AccelMapPage.cpp
  • AccelListBox.h, AccelListBox.cpp
  • CmdAccelOb.h, CmdAccelOb.cpp
  • KeyboardEdit.h, KeyboardEdit.cpp
  • Registry.h, Registry.cpp (Copyright (C) 1998 by Shane Martin, modified to avoid an error when reading the registry)



Comments

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds