48.2.2 Using an Accelerator Table Created at Run Time

Win32 allows you to create accelerator tables at run time. You can create and use an accelerator table at run time by following these steps:

Define your accelerators by filling an array of ACCEL structures, then create an accelerator table by passing the array to the CreateAcceleratorTable function.

Activate the accelerator table and process WM_COMMAND messages generated by the accelerators.

Destroy the accelerator table before your application exits.

The remainder of this section describes the details involved in creating and using accelerator tables at run time.

48.2.2.1 Creating the Accelerator Table

The first step in creating an accelerator table at run time is filling an array of ACCEL structures. Each structure in the array defines an accelerator in the table. An accelerator's definition includes its keystroke, its identifier, and its style flags. The ACCEL structure has the following form:

typedef struct tagACCEL { /* accl */

BYTE fVirt;

WORD key;

WORD cmd;

} ACCEL, *LPACCEL;

You define an accelerator's keystroke by specifying an ASCII character code or a virtual-key code in the key member of the ACCEL structure. If you specify a virtual-key code, you must also include the FVIRTKEY flag in the fVirt member; otherwise, Windows interprets the code as an ASCII character code. You can include the FCONTROL, FALT, or FSHIFT flag, or all three, to combine the CTRL, ALT, or SHIFT key with the keystroke.

To create the accelerator table, you need to pass the address of the array to the CreateAcceleratorTable function. CreateAcceleratorTable creates the accelerator table and returns the handle of the table.

48.2.2.2 Processing Accelerators

Processing accelerators that have been provided by an accelerator table created at run time is the same as processing those provided by an accelerator table resource. For more information, see Section 0.2.1.2, “Loading the Accelerator-Table Resource” through Section 0.2.1.4, “Processing WM_COMMAND Messages.”

48.2.2.3 Destroying the Accelerator Table

Before your application exits, it must destroy any accelerator tables that it may have created at run time. You can destroy an accelerator table by passing the table's handle to the DestroyAcceleratorTable function.

48.2.2.4 Example: User-Editable Accelerators

This section contains code for a dialog box that allows the user to change the accelerator associated with a menu item. The dialog box consists of a combo box containing menu items, a combo box containing the names of keys, and check boxes for selecting the CTRL, ALT, and SHIFT keys. The following illustration shows the dialog box:

Edit Accelerators Dialog Box

The following example shows how the dialog box is defined in the resource-definition file:

EdAccelBox DIALOG 5, 17, 193, 114

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION

CAPTION "Edit Accelerators"

FONT 8, "Helv"

BEGIN

COMBOBOX IDD_MENUITEMS, 10, 22, 52, 53,

CBS_SIMPLE | CBS_SORT | WS_VSCROLL |

WS_TABSTOP

CONTROL "Control", IDD_CNTRL, "Button",

BS_AUTOCHECKBOX | WS_TABSTOP,

76, 35, 40, 10

CONTROL "Alt", IDD_ALT, "Button",

BS_AUTOCHECKBOX | WS_TABSTOP,

76, 48, 40, 10

CONTROL "Shift", IDD_SHIFT, "Button",

BS_AUTOCHECKBOX | WS_TABSTOP,

76, 61, 40, 10

COMBOBOX IDD_KEYSTROKES, 124, 22, 58, 58,

CBS_SIMPLE | CBS_SORT | WS_VSCROLL |

WS_TABSTOP

PUSHBUTTON "Ok", IDOK, 43, 92, 40, 14

PUSHBUTTON "Cancel", IDCANCEL, 103, 92, 40, 14

LTEXT "Select Item:", 101, 10, 12, 43, 8

LTEXT "Select Keystroke:", 102, 123, 12, 60, 8

END

The dialog box uses an array of application-defined VKEY structures, each containing a keystroke-text string and an accelerator-text string. When the dialog box is created, it parses the array and adds each keystroke-text string to the Select Keystroke combo box. When the user clicks the Ok button, the dialog box looks up the selected keystroke-text string and retrieves the corresponding accelerator-text string. The dialog box appends the accelerator-text string to the text of the menu item that the user selected. The following example shows the array of VKEY structures:

/* VKey Lookup Support */

#define MAXKEYS 26

typedef struct _VKEYS {

char *pKeyName;

char *pKeyString;

} VKEYS;

VKEYS vkeys[MAXKEYS] = {

"BkSp", "Back Space",

"PgUp", "Page Up",

"PgDn", "Page Down",

"End", "End",

"Home", "Home",

"Lft", "Left",

"Up", "Up",

"Rgt", "Right",

"Dn", "Down",

"Ins", "Insert",

"Del", "Delete",

"Mult", "Multiply",

"Add", "Add",

"Sub", "Subtract",

"DecPt", "Decimal Point",

"Div", "Divide",

"F2", "F2",

"F3", "F3",

"F5", "F5",

"F6", "F6",

"F7", "F7",

"F8", "F8",

"F9", "F9",

"F11", "F11",

"F12", "F12"

};

The dialog box's initialization procedure fills the Select Item and Select Keystroke combo boxes. After the user selects a menu item and associated accelerator, the dialog box examines the controls in the dialog box to get the user's selection, updates the text of the menu item, then creates a new accelerator table that contains the user-defined new accelerator. The following example shows the dialog-box procedure:

/* Global variables */

HWND hwndMain; /* handle of main window */

HANDLE hinstAcc; /* handle of application instance */

HACCEL haccel; /* handle of accelerator table */

.

.

.

/* Dialog box procedure */

LONG APIENTRY EdAccelProc(hwndDlg, uMsg, wParam, lParam)

HWND hwndDlg;

UINT uMsg;

UINT wParam;

LONG lParam;

{

int nCurSel; /* index of list box item */

UINT idItem; /* menu item identifier */

UINT uItemPos; /* menu item position */

UINT i, j = 0; /* loop counters */

static UINT cItems; /* count of items in menu */

char szTemp[32]; /* temporary buffer */

char szAccelText[32]; /* buffer for accelerator text */

char szKeyStroke[16]; /* buffer for keystroke text */

static char szItem[32]; /* buffer for menu-item text */

HWND hwndCtl; /* handle of control window */

static HMENU hmenu; /* handle of "Character" menu */

PCHAR pch, pch2; /* pointers for string copying */

WORD wVKCode; /* accelerator virtual-key code */

BYTE fAccelFlags; /* fVirt flags for ACCEL structure */

LPACCEL lpaccelNew; /* address of new accel. table */

HACCEL haccelOld; /* handle of old accel. table */

int cAccelerators; /* number of accelerators in table */

static BOOL fItemSelected = FALSE; /* item selection flag */

static BOOL fKeySelected = FALSE; /* key selection flag */

switch (uMsg) {

case WM_INITDIALOG:

/* Get the handle of the menu-item combo box. */

hwndCtl = GetDlgItem(hwndDlg, IDD_MENUITEMS);

/*

* The application's menu bar contains a "Character"

* submenu whose items have accelerators associated

* with them. Get the handle of the "Character"

* submenu (its position within the main menu is 2),

* and count the number of items it has.

*/

hmenu = GetSubMenu(GetMenu(hwndMain), 2);

cItems = GetMenuItemCount(hmenu);

/*

* Get the text of each item, strip out the '&' and

* the accelerator text, and add the text to the

* menu-item combo box.

*/

for (i = 0; i < cItems; i++) {

if (!(GetMenuString(hmenu, i, szTemp,

sizeof(szTemp), MF_BYPOSITION)))

continue;

for (pch = szTemp, pch2 = szItem;

*pch != '\0'; ) {

if (*pch != '&') {

if (*pch == '\t') {

*pch = '\0';

*pch2 = '\0';

}

else

*pch2++ = *pch++;

}

else

pch++;

}

SendMessage(hwndCtl, CB_ADDSTRING, 0,

(LONG) (LPSTR) szItem);

}

/*

* Now fill the keystroke combo box with the list of

* keystrokes that will be allowed for accelerators.

* The list of keystrokes is in the application-defined

* structure called "vkeys".

*/

hwndCtl = GetDlgItem(hwndDlg, IDD_KEYSTROKES);

for (i = 0; i < MAXKEYS; i++)

SendMessage(hwndCtl, CB_ADDSTRING, 0,

(LONG) (LPSTR) vkeys[i].pKeyString);

return TRUE;

case WM_COMMAND:

switch (LOWORD(wParam)) {

case IDD_MENUITEMS:

/*

* The user must select an item from the menu-

* item combo box. This flag is checked

* during IDOK processing to be sure a sel-

* ection was made.

*/

fItemSelected = TRUE;

return 0;

case IDD_KEYSTROKES:

/*

* The user must select an item from the menu-

* item combo box. This flag is checked

* during IDOK processing to be sure a sel-

* ection was made.

*/

fKeySelected = TRUE;

return 0;

case IDOK:

/*

* If the user hasn't selected a menu item

* and a keystroke, display a reminder in a

* message box.

*/

if (!fItemSelected || !fKeySelected) {

MessageBox(hwndDlg,

"Item or key not selected.", NULL,

MB_OK);

return 0;

}

/*

* Determine which of CNTL, ALT, and SHIFT

* are selected. Concatenate the

* appropriate strings to the accelerator-

* text buffer, and set the appropriate

* accelerator flags.

*/

szAccelText[0] = '\0';

hwndCtl = GetDlgItem(hwndDlg, IDD_CNTRL);

if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0)

== 1) {

lstrcat(szAccelText, "Ctl+");

fAccelFlags |= FCONTROL;

}

hwndCtl = GetDlgItem(hwndDlg, IDD_ALT);

if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0)

== 1) {

lstrcat(szAccelText, "Alt+");

fAccelFlags |= FALT;

}

hwndCtl = GetDlgItem(hwndDlg, IDD_SHIFT);

if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0)

== 1) {

lstrcat(szAccelText, "Shft+");

fAccelFlags |= FSHIFT;

}

/*

* Get selected keystroke and look up the

* accelerator text and the virtual-key code

* for the keystroke in the vkeys structure.

*/

hwndCtl = GetDlgItem(hwndDlg, IDD_KEYSTROKES);

nCurSel = (int) SendMessage(hwndCtl,

CB_GETCURSEL, 0, 0);

SendMessage(hwndCtl, CB_GETLBTEXT,

nCurSel, (LONG) (LPSTR) szKeyStroke);

for (i = 0; i < MAXKEYS; i++) {

if(lstrcmp(vkeys[i].pKeyString,

szKeyStroke) == 0) {

lstrcpy(szKeyStroke,

vkeys[i].pKeyName);

break;

}

}

/*

* Concatenate the keystroke text to the

* "Ctl+","Alt+", or "Shft+" string.

*/

lstrcat(szAccelText, szKeyStroke);

/*

* Determine the position in the menu of the

* selected menu item. 0, 2, 3, and 4 are the

* positions of the menu items in the

* "Character" menu.

*/

if (lstrcmp(szItem, "Regular") == 0)

uItemPos = 0;

else if (lstrcmp(szItem, "Bold") == 0)

uItemPos = 2;

else if (lstrcmp(szItem, "Italic") == 0)

uItemPos = 3;

else if (lstrcmp(szItem, "Underline") == 0)

uItemPos = 4;

/*

* Get the string that corresponds to the

* selected item.

*/

GetMenuString(hmenu, uItemPos, szItem,

sizeof(szItem), MF_BYPOSITION);

/*

* Append the new accelerator text to the

* menu-item text.

*/

for (pch = szItem; *pch != '\t'; pch++);

++pch;

for (pch2 = szAccelText; *pch2 != '\0';

pch2++)

*pch++ = *pch2;

*pch = '\0';

/*

* Modify the menu item to reflect the new

* accelerator text.

*/

idItem = GetMenuItemID(hmenu, uItemPos);

ModifyMenu(hmenu, idItem, MF_BYCOMMAND |

MF_STRING, idItem, szItem);

/* Reset the selection flags. */

fItemSelected = FALSE;

fKeySelected = FALSE;

/* Save the current accelerator table. */

haccelOld = haccel;

/*

* Count the number of entries in the current

* table, allocate a buffer for the table,

* then copy the table into the buffer.

*/

cAccelerators = CopyAcceleratorTable(

haccelOld, NULL, 0);

lpaccelNew = (LPACCEL) LocalAlloc(LPTR,

cAccelerators * sizeof(ACCEL));

if (lpaccelNew != NULL)

CopyAcceleratorTable(haccel, lpaccelNew,

cAccelerators);

/*

* Find the accelerator that the user modified

* and change its flags and virtual-key code

* as appropriate.

*/

for (i = 0; (lpaccelNew[i].cmd == (WORD) idItem)

&& (i < (UINT) cAccelerators); i++) {

lpaccelNew[i].fVirt = fAccelFlags;

lpaccelNew[i].key = wVKCode;

}

/*

* Create the new accelerator table and

* destroy the old one.

*/

DestroyAcceleratorTable(haccelOld);

haccel = CreateAcceleratorTable(lpaccelNew,

cAccelerators);

/* Destroy the dialog box. */

EndDialog(hwndDlg, TRUE);

return 0;

case IDCANCEL:

EndDialog(hwndDlg, TRUE);

return TRUE;

default:

break;

}

default:

break;

}

return FALSE;

}