Creating User-Editable Accelerators

This example shows how to construct 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.

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"

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, and 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 */

LRESULT CALLBACK EdAccelProc(hwndDlg, uMsg, wParam, lParam)

HWND hwndDlg;

UINT uMsg;

WPARAM wParam;

LPARAM 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 selection 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 selection was made.

*/

fKeySelected = TRUE;

return 0;

case IDOK:

/*

* If the user has not 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 whether the CTRL, ALT, and SHIFT

* keys 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 the 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. Menu items in the

* "Character" menu have positions 0,2,3, and 4.

*/

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, and

* 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;

}