Objective Toolkit : Chapter 15 User Interface Extensions : Keyboard Shortcuts
Keyboard Shortcuts
The keyboard shortcut classes enable users to redefine the keyboard for your application. These classes enable end-users to update the accelerator table at run time by choosing key bindings.
The Keyboard Shortcut Classes
The following figure is the class hierarchy for the Keyboard Shortcut Classes.
Figure 118 – Keyboard Shortcut Class Hierarchy
SECShortcutTable
SECShortcutTable contains key bindings in the form of an array of ACCELs.
SECCommandList
SECCommandList contains a list of every command IDs that you can assign together with a short and a long description. You can default any and all parts of SECCommandList.
SECShortcutDlg
SECShortcutDlg is a dialog class that provides a front-end for entering application macros. It presents a standard dialog for accelerator key entry to the application.
NOTE >> The Objective Toolkit shortcut classes only use the APIs found in Win32.
Using the Keyboard Shortcut Classes
The following sections describe how to use keyboard shortcut classes in your application.
To incorporate keyboard shortcuts into your application
1. At the end of InitInstance(), add this code to load user-assigned shortcuts.
 
SECShortcutTable shortcuts;
if (shortcuts.Load())
{
shortcuts.Apply();
}
2. To invoke the shortcut dialog, use the following code. Generally, you can handle the shortcut dialog at the main frame window level because the shortcut handling is not view or document specific.
 
void OnAssignShortcuts()
{
SECCommandList commands;
SECShortcutTable shortcuts;
 
SECShortcutDlg dlg(commands, shortcuts);
 
if (dlg.DoModal() == IDOK && dlg.m_bDirty)
{
shortcuts.Save();
shortcuts.Apply();
}
}
To update menus
The application updates the menus automatically to show the keys defined in the current accelerator table. Any accelerator descriptions you put into menu items in the resource file are discarded.
If you have menu items that modify themselves in their OnUpdate() method, you need to ensure that you preserve the accelerator that is currently defined. For example, the user can redefine the keystrokes for Undo. If your application changes the Undo menu to describe the last action, you need to preserve the text of the current accelerator.
To allow or disallow certain keyboard combinations
1. Complete the steps in “To incorporate keyboard shortcuts into your application.”
2. Before you invoke the SECShortcutDlg dialog and after you instantiate the SECCommandList object, call the SECCommandList::SetRules() method. For example:
 
// The default, appropriate for programs
// that deal with text.
commands.SetRules(HKCOMB_NONE|HKCOMB_S,
HOTKEYF_CONTROL);
 
// A good alternative for draw programs
// that never need
// character input in the main window.
commands.SetRules(0,0); // Allow all keys, even
// unmodified letters
 
// Very restrictive -- only CTRL+ALT
// combinations will
// be allowed, and anything else will
// convert to a CTRL+ALT
commands.SetRules((WORD)~HKCOMB_SC, HOTKEYF_CONTROL|HOTKEYF_ALT);
For more information on this method, see the documentation for CHotKeyCtrl::SetRules() in the Objective Toolkit Class Reference.
Setting Up Commands
A default list of command IDs is automatically generated when you declare an instance of SECCommandList. This list is generated when the application reads the main frame window and template menus to look for IDs. The name of the macro is the menu sequence (for example, File:Open). The long description is the string resource of the same ID. This string resource is typically shown in the status line when the user highlights a menu item.
You can either replace or add to this list of command IDs. For example:
 
const SECDefaultCommandId defaultCommands[] =
{
{ ID_VIEW_TOOLBAR, IDS_MAC_VIEW_TOOLBAR, IDS_DESC_VIEW_TOOLBAR },
{ ID_FILE_OPEN, 0,
IDS_DESC_FILE_OPEN }, // Default name
{ ID_FILE_SAVE, IDS_MAC_FILE_SAVE,
0 }, // Default description
{ ID_FILE_PRINT } // Default both
}; SECCommandList commands;
 
SECCommandList commands;
 
commands.ClearCommandIds();
commands.AddCommandIds(defaultCommands,
sizeof(defaultCommands)/
sizeof(SECDefaultCommandId));
In the above example, the elements defined in defaultCommands comprise a custom list of IDs. These are the only command IDs that you can assign. The call to ClearCommandIds() removes each of the default IDs that you set up by default in the constructor for SECCommandList. The call to AddCommandIds() installs the custom list of IDs.
This example also shows how to customize the names and descriptions that the dialog uses. Here is the definition for SECDefaultCommandId().
 
struct SECDefaultCommandId
{
// Id of this command, such as ID_VIEW_TOOLBAR
UINT m_nID;
 
// String ID that gives the short name of the
// command. This name appears in the
// "Select a macro:" listbox. If this is
// zero, then the menu name or toolhelp
// text is used.
UINT m_nName;
 
// String ID that gives the description of
// the command. This name appears in the
// "Description:" listbox. If this is zero,
// then the status bar text for this
// id is used.
UINT m_nDescription;
};
As the definition of defaultCommands in the preceding example indicates, you can leave m_nName or m_nDescription (or both) at zero, so SECCommandList uses either the menu name or the corresponding string resource to find the name or description (or both).
If you remove an ID from this list during the course of development, the saved file can still use it. Select Reset All in the dialog to solve this problem.
NOTE >> Visual Studio does perform a dependency check on resource.h. Resource.h defines the values for every string and command ID. If you change the value of an ID when you are using a custom table, Visual Studio does not automatically recompile the module that contains the table. This can result in erratic behavior in the dialog class.
Excluded IDs
Objective Toolkit automatically excludes some IDs even if you put them in a list because you cannot assign them or because you never would assign them. The virtual member function QueryExcludeId() in SECCommandList contains the default list of excluded IDs. When you derive your own class from SECCommandList, you can either add IDs to the list or replace this list. A new constructor is also needed for the derived class to change the default behavior of the base SECCommandList class. For example,
 
class MyCommandList : public SECCommandList
{
public:
MyCommandList(): SECCommandList(FALSE)
{
SetRules(HKCOMB_NONE|HKCOMB_S, HOTKEYF_CONTROL);
DeriveDefaults();
}
public:
virtual BOOL QueryExcludeId (UINT nID);
};
The default list of excluded IDs is as follows:
MRU entries on the File menu
MDI child entries on the Window menu
ID_NEXT_PANE and ID_PREV_PANE
Although ID_HELP and ID_CONTEXT_HELP are not excluded by default, you might want to exclude them. You cannot reliably assign commands to these IDs.
Saving the Shortcuts
The shortcuts are automatically saved to a file named <application-name>.MAC. If possible, store this file in the same directory as the executable. Otherwise it is put in the Windows directory.
The filename is generated by the member function GetDataFileName() in SECShortcutTable. This function returns a fully qualified name and path. This function is virtual and can be overridden. The second argument to this function specifies either MAIN_NAME or ALTERNATE_NAME. This is the mechanism that switches from the application directory to the Windows directory.
You can also replace the storage mechanism completely by overriding Load() and Save() in SECShortcutTable. You would do this if you wanted to save the table in the registry. You can write the table to any variant of a CArchive object.
The shortcuts are loaded and applied in InitInstance() of the application class.
Keyboard Shortcut Notes
The keyboard shortcut classes support multiple doc templates and multiple view menus. When you load and save the list of shortcuts, the shortcut classes scan every doc-template that is associated with the application object and then updates the menu text to display the shortcut key. This only occurs if each menu has an unique command ID. Overlapping command IDs are resolved in the context of the active document's menu.
Menus are updated recursively through all levels of pop-up sub-menus to support cascading menus.
If you create an ID that appears on a toolbar but not in a menu, the shortcut classes use the Toolhelp string resource to generate a name and description.
These classes support international and multi-byte versions of Windows.
These classes presume that a single accelerator table exists in the main frame window. View-specific accelerator tables are not supported.
Multiple main frame windows are not supported.
Keyboard Shortcut Sample
The Objective Toolkit Shortcut sample is called ShortCut. It shows how to add shortcuts to a standard AppWizard-generated MDI application. This sample does not ship with the product. For information on how to obtain this sample, see “Location of Sample Code” in the Getting Started part.