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)