Использование сочетаний клавиш

В этом разделе рассматриваются задачи, связанные с ускорителями клавиатуры.

Использование ресурса таблицы ускорителя

Наиболее распространенным способом добавления поддержки акселератора в приложение является включение ресурса таблицы ускорителя с исполняемым файлом приложения, а затем загрузка ресурса во время выполнения.

В этом разделе рассматриваются следующие разделы.

Создание ресурса таблицы ускорителя

Ресурс accelerator-table создается с помощью инструкции ACCELERATORS в файле определения ресурсов приложения. Необходимо назначить имя или идентификатор ресурса в таблицу акселератора, предпочтительно в отличие от любого другого ресурса. Система использует этот идентификатор для загрузки ресурса во время выполнения.

Для каждого определенного ускорителя требуется отдельная запись в таблице акселератора. В каждой записи определяется нажатие клавиш (код символа ASCII или код виртуального ключа), который создает ускоритель и идентификатор акселератора. Необходимо также указать, следует ли использовать нажатие клавиши в сочетании с клавишами ALT, SHIFT или CTRL. Дополнительные сведения о виртуальных ключах см. в разделе "Ввод клавиатуры".

Нажатие клавиш ASCII задается путем заключения символа ASCII в двойные кавычки или с помощью целочисленного значения символа в сочетании с флагом ASCII. В следующих примерах показано, как определить акселераторы ASCII.

"A", ID_ACCEL1         ; SHIFT+A 
65,  ID_ACCEL2, ASCII  ; SHIFT+A 

Нажатие клавиш в коде виртуального ключа задается по-разному в зависимости от того, является ли нажатие клавиш буквенно-цифровым ключом или не буквенно-цифровым ключом. Для буквенно-цифрового ключа буквы или цифры ключа, заключенные в двойные кавычки, объединяются с флагом VIRTKEY . Для небуквенно-цифрового ключа код виртуального ключа для конкретного ключа объединяется с флагом VIRTKEY . В следующих примерах показано, как определить ускорители кода виртуального ключа.

"a",       ID_ACCEL3, VIRTKEY   ; A (caps-lock on) or a 
VK_INSERT, ID_ACCEL4, VIRTKEY   ; INSERT key 

В следующем примере показан ресурс таблицы ускорителей, определяющий ускорители для операций с файлами. Имя ресурса — FileAccel.

FileAccel ACCELERATORS 
BEGIN 
    VK_F12, IDM_OPEN, CONTROL, VIRTKEY  ; CTRL+F12 
    VK_F4,  IDM_CLOSE, ALT, VIRTKEY     ; ALT+F4 
    VK_F12, IDM_SAVE, SHIFT, VIRTKEY    ; SHIFT+F12 
    VK_F12, IDM_SAVEAS, VIRTKEY         ; F12 
END 

Если вы хотите, чтобы пользователь нажимал клавиши ALT, SHIFT или CTRL в сочетании с нажатием клавиши сочетания клавиш, укажите флаги ALT, SHIFT и CONTROL в определении акселератора. Ниже приводятся некоторые примеры.

"B",   ID_ACCEL5, ALT                   ; ALT_SHIFT+B 
"I",   ID_ACCEL6, CONTROL, VIRTKEY      ; CTRL+I 
VK_F5, ID_ACCEL7, CONTROL, ALT, VIRTKEY ; CTRL+ALT+F5 

По умолчанию, когда клавиша ускорителя соответствует элементу меню, система выделяет пункт меню. Флаг NOINVERT можно использовать для предотвращения выделения для отдельного акселератора. В следующем примере показано, как использовать флаг NOINVERT :

VK_DELETE, ID_ACCEL8, VIRTKEY, SHIFT, NOINVERT  ; SHIFT+DELETE 

Чтобы определить ускорители, соответствующие пунктам меню в приложении, включите ускорители в текст элементов меню. В следующем примере показано, как включить ускорители в текст элемента меню в файл определения ресурса.

FilePopup MENU 
BEGIN 
    POPUP   "&File" 
    BEGIN 
        MENUITEM    "&New..",           IDM_NEW 
        MENUITEM    "&Open\tCtrl+F12",  IDM_OPEN 
        MENUITEM    "&Close\tAlt+F4"    IDM_CLOSE 
        MENUITEM    "&Save\tShift+F12", IDM_SAVE 
        MENUITEM    "Save &As...\tF12", IDM_SAVEAS 
    END 
END 

Загрузка ресурса таблицы ускорителя

Приложение загружает ресурс таблицы ускорителя, вызывая функцию LoadAccelerators и указывая дескриптор экземпляра для приложения, исполняемый файл которого содержит ресурс и имя или идентификатор ресурса. LoadAccelerators загружает указанную таблицу ускорителя в память и возвращает дескриптор в таблицу ускорителя.

Приложение может в любой момент загрузить ресурс таблицы ускорителя. Как правило, однопоточное приложение загружает таблицу акселератора перед вводом основного цикла сообщений. Приложение, использующее несколько потоков, обычно загружает ресурс ускорителя таблицы для потока перед вводом цикла сообщений для потока. Приложение или поток также могут использовать несколько таблиц ускорителей, каждый из которых связан с определенным окном в приложении. Такое приложение будет загружать таблицу акселератора для окна каждый раз, когда пользователь активировал окно. Дополнительные сведения о потоках см. в разделе "Процессы и потоки".

Вызов функции ускорителя перевода

Для обработки акселераторов цикл сообщений приложения (или потока) должен содержать вызов функции TranslateAccelerator . TranslateAccelerator сравнивает нажатия клавиш с таблицей ускорителя и, если он находит совпадение, преобразует нажатия клавиш в сообщение WM_COMMAND (или WM_SYSCOMMAND). Затем функция отправляет сообщение в процедуру окна. Параметры функции TranslateAccelerator включают дескриптор в окно, которое должно получать WM_COMMAND сообщения, дескриптор таблицы ускорителей, используемой для перевода ускорителей, и указатель на структуру MSG , содержащую сообщение из очереди. В следующем примере показано, как вызвать TranslateAccelerator из цикла сообщений.

MSG msg;
BOOL bRet;

while ( (bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
    if (bRet == -1) 
    {
        // handle the error and possibly exit
    }
    else
    { 
        // Check for accelerator keystrokes. 
     
        if (!TranslateAccelerator( 
                hwndMain,      // handle to receiving window 
                haccel,        // handle to active accelerator table 
                &msg))         // message data 
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
}

Обработка сообщений WM_COMMAND

При использовании ускорителя окно, указанное в функции TranslateAccelerator , получает сообщение WM_COMMAND или WM_SYSCOMMAND . Слово с низким порядком параметра wParam содержит идентификатор акселератора. Процедура окна проверяет идентификатор, чтобы определить источник сообщения WM_COMMAND и обработать сообщение соответствующим образом.

Как правило, если ускоритель соответствует элементу меню в приложении, ускоритель и пункт меню назначаются одинаковым идентификатором. Если необходимо знать, было ли сообщение WM_COMMAND создано акселератором или элементом меню, можно изучить слово высокого порядка параметра wParam . Если акселератор создал сообщение, слово высокого порядка равно 1; Если элемент меню создал сообщение, слово высокого порядка равно 0.

Уничтожение ресурса таблицы ускорителя

Система автоматически уничтожает ресурсы таблицы ускорителей, загруженные функцией LoadAccelerators , удаляя ресурс из памяти после закрытия приложения.

Создание ускорителей для атрибутов шрифта

В примере в этом разделе показано, как выполнять следующие задачи:

  • Создайте ресурс ускорителя таблицы.
  • Загрузите таблицу акселератора во время выполнения.
  • Перевод ускорителей в цикле сообщений.
  • Обработка WM_COMMAND сообщений, созданных ускорителями.

Эти задачи демонстрируются в контексте приложения, включающего меню символов и соответствующие ускорители, позволяющие пользователю выбирать атрибуты текущего шрифта.

Следующая часть файла определения ресурса определяет меню символов и связанную таблицу ускорителей. Обратите внимание, что в пунктах меню отображаются нажатия клавиш акселератора и что каждый акселератор имеет тот же идентификатор, что и связанный пункт меню.

#include <windows.h> 
#include "acc.h" 
 
MainMenu MENU 
{ 
    POPUP   "&Character" 
    { 
        MENUITEM    "&Regular\tF5",         IDM_REGULAR 
        MENUITEM    "&Bold\tCtrl+B",        IDM_BOLD 
        MENUITEM    "&Italic\tCtrl+I",      IDM_ITALIC 
        MENUITEM    "&Underline\tCtrl+U",   IDM_ULINE 
    }
} 
 
FontAccel ACCELERATORS 
{ 
    VK_F5,  IDM_REGULAR,    VIRTKEY 
    "B",    IDM_BOLD,       CONTROL, VIRTKEY 
    "I",    IDM_ITALIC,     CONTROL, VIRTKEY 
    "U",    IDM_ULINE,      CONTROL, VIRTKEY 
}
 

В следующих разделах исходного файла приложения показано, как реализовать ускорители.

HWND hwndMain;      // handle to main window 
HANDLE hinstAcc;    // handle to application instance 
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow) 
{ 
    MSG msg;            // application messages 
    BOOL bRet;          // for return value of GetMessage
    HACCEL haccel;      // handle to accelerator table 
 
    // Perform the initialization procedure. 
 
    // Create a main window for this application instance. 
 
    hwndMain = CreateWindowEx(0L, "MainWindowClass", 
        "Sample Application", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, 
        hinst, NULL ); 
 
    // If a window cannot be created, return "failure." 
 
    if (!hwndMain) 
        return FALSE; 
 
    // Make the window visible and update its client area. 
 
    ShowWindow(hwndMain, nCmdShow); 
    UpdateWindow(hwndMain); 
 
    // Load the accelerator table. 
 
    haccel = LoadAccelerators(hinstAcc, "FontAccel"); 
    if (haccel == NULL) 
        HandleAccelErr(ERR_LOADING);     // application defined 
 
    // Get and dispatch messages until a WM_QUIT message is 
    // received. 
 
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
    {
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        { 
            // Check for accelerator keystrokes. 
     
            if (!TranslateAccelerator( 
                    hwndMain,  // handle to receiving window 
                    haccel,    // handle to active accelerator table 
                    &msg))         // message data 
            {
                TranslateMessage(&msg); 
                DispatchMessage(&msg); 
            } 
        } 
    }
    return msg.wParam; 
} 
 
LRESULT APIENTRY MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
    BYTE fbFontAttrib;        // array of font-attribute flags 
    static HMENU hmenu;       // handle to main menu 
 
    switch (uMsg) 
    { 
        case WM_CREATE: 
 
            // Add a check mark to the Regular menu item to 
            // indicate that it is the default. 
 
            hmenu = GetMenu(hwndMain); 
            CheckMenuItem(hmenu, IDM_REGULAR, MF_BYCOMMAND | 
                MF_CHECKED); 
            return 0; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                // Process the accelerator and menu commands. 
 
                case IDM_REGULAR: 
                case IDM_BOLD: 
                case IDM_ITALIC: 
                case IDM_ULINE: 
 
                    // GetFontAttributes is an application-defined 
                    // function that sets the menu-item check marks 
                    // and returns the user-selected font attributes. 
 
                    fbFontAttrib = GetFontAttributes( 
                        (BYTE) LOWORD(wParam), hmenu); 
 
                    // SetFontAttributes is an application-defined 
                    // function that creates a font with the 
                    // user-specified attributes the font with 
                    // the main window's device context. 
 
                    SetFontAttributes(fbFontAttrib); 
                    break; 
 
                default: 
                    break; 
            } 
            break; 
 
            // Process other messages. 
 
        default: 
            return DefWindowProc(hwndMain, uMsg, wParam, lParam); 
    } 
    return NULL; 
}

Использование таблицы ускорителя, созданной во время выполнения

В этом разделе описывается использование таблиц ускорителей, созданных во время выполнения.

Создание таблицы акселератора Run-Time

Первым шагом при создании таблицы акселератора во время выполнения является заполнение массива структур ACCEL . Каждая структура в массиве определяет ускоритель в таблице. Определение ускорителя включает его флаги, ключ и идентификатор. Структура ACCEL имеет следующую форму.

typedef struct tagACCEL { // accl 
    BYTE   fVirt; 
    WORD   key; 
    WORD   cmd; 
} ACCEL;

Чтобы определить нажатие клавиш акселератора, укажите код символа ASCII или код виртуального ключа в элементе ключа структуры ACCEL . При указании кода виртуального ключа необходимо сначала включить флаг FVIRTKEY в член fVirt ; в противном случае система интерпретирует код как код символа ASCII. Можно включить флаг FCONTROL, FALT или FSHIFT или все три, чтобы объединить клавиши CTRL, ALT или SHIFT с нажатием клавиш.

Чтобы создать таблицу ускорителей, передайте указатель на массив структур ACCEL в функцию CreateAcceleratorTable . CreateAcceleratorTable создает таблицу ускорителя и возвращает дескриптор в таблицу.

Ускорители обработки

Процесс загрузки и вызова ускорителей, предоставляемых таблицей ускорителей, созданной во время выполнения, совпадает с обработкой, предоставляемой ресурсом таблицы ускорителя. Дополнительные сведения см. в разделе "Загрузка ресурса таблицы ускорителя " с помощью обработки WM_COMMAND сообщений.

Уничтожение таблицы ускорителей Run-Time

Система автоматически уничтожает таблицы ускорителей, созданные во время выполнения, удаляя ресурсы из памяти после закрытия приложения. Можно уничтожить таблицу ускорителя и удалить ее из памяти ранее, передав дескриптор таблицы в функцию DestroyAcceleratorTable .

Создание пользовательских редактируемых ускорителей

В этом примере показано, как создать диалоговое окно, позволяющее пользователю изменить ускоритель, связанный с элементом меню. Диалоговое окно состоит из поля со списком, содержащего пункты меню, поле со списком, содержащее имена клавиш и флажки для выбора клавиш CTRL, ALT и SHIFT. На следующем рисунке показано диалоговое окно.

dialog box with combo boxes and check boxes

В следующем примере показано, как диалоговое окно определено в файле определения ресурса.

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

Строка меню приложения содержит подменю символов , элементы которых имеют связанные с ними ускорители.

MainMenu MENU 
{ 
    POPUP "&Character" 
    { 
        MENUITEM    "&Regular\tF5",         IDM_REGULAR 
        MENUITEM    "&Bold\tCtrl+B",        IDM_BOLD 
        MENUITEM    "&Italic\tCtrl+I",      IDM_ITALIC 
        MENUITEM    "&Underline\tCtrl+U",   IDM_ULINE 
    }
} 
 
FontAccel ACCELERATORS 
{ 
    VK_F5,  IDM_REGULAR,    VIRTKEY 
    "B",    IDM_BOLD,       CONTROL, VIRTKEY 
    "I",    IDM_ITALIC,     CONTROL, VIRTKEY 
    "U",    IDM_ULINE,      CONTROL, VIRTKEY 
}
 

Значения элементов меню для шаблона меню — это константы, определенные следующим образом в файле заголовка приложения.

#define IDM_REGULAR    1100
#define IDM_BOLD       1200
#define IDM_ITALIC     1300
#define IDM_ULINE      1400

В диалоговом окне используется массив структур VKEY, определенных приложением, каждый из которых содержит текстовую строку нажатия клавиш и строку сочетания текста. При создании диалогового окна он анализирует массив и добавляет каждую строку нажатия клавиши в поле со списком "Выбор клавиш ". Когда пользователь нажимает кнопку "ОК ", диалоговое окно ищет выбранную строку нажатия клавиш и извлекает соответствующую строку сочетания текста. Диалоговое окно добавляет строку ускорителя текста к тексту выбранного пользователем пункта меню. В следующем примере показан массив структур VKEY:

// VKey Lookup Support 
 
#define MAXKEYS 25 
 
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" 
};

Процедура инициализации диалогового окна заполняет поля со списком «Выбор элемента » и «Выбор клавиш». Когда пользователь выбирает пункт меню и связанный ускоритель, диалоговое окно проверяет элементы управления в диалоговом окне, чтобы получить выбор пользователя, обновляет текст элемента меню, а затем создает новую таблицу ускорителя, содержащую определяемый пользователем новый ускоритель. В следующем примере показана процедура диалогового окна. Обратите внимание, что необходимо инициализировать процедуру окна.

// Global variables 
 
HWND hwndMain;      // handle to main window 
HACCEL haccel;      // handle to accelerator table 
 
// Dialog-box procedure 
 
BOOL CALLBACK EdAccelProc(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 to control window 
    static HMENU hmenu;     // handle to "Character" menu 
    PCHAR pch, pch2;        // pointers for string copying 
    WORD wVKCode;           // accelerator virtual-key code 
    BYTE fAccelFlags;       // fVirt flags for ACCEL structure 
    LPACCEL lpaccelNew;     // pointer to new accelerator table 
    HACCEL haccelOld;       // handle to old accelerator table 
    int cAccelerators;      // number of accelerators in table 
    static BOOL fItemSelected = FALSE; // item selection flag 
    static BOOL fKeySelected = FALSE;  // key selection flag 
    HRESULT hr;
    INT numTCHAR;           // TCHARs in listbox text
 
    switch (uMsg) 
    { 
        case WM_INITDIALOG: 
 
            // Get the handle to the menu-item combo box. 
 
            hwndCtl = GetDlgItem(hwndDlg, IDD_MENUITEMS); 
 
            // Get the handle to the Character submenu and
            // count the number of items it has. In this example, 
            // the menu has position 0. You must alter this value 
            // if you add additional menus. 
            hmenu = GetSubMenu(GetMenu(hwndMain), 0); 
            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)/sizeof(TCHAR), 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 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 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) 
                    { 
                        hr = StringCchCat(szAccelText, 32, "Ctl+");
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
                        fAccelFlags |= FCONTROL; 
                    } 
                    hwndCtl = GetDlgItem(hwndDlg, IDD_ALT); 
                    if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1) 
                    { 
                        hr = StringCchCat(szAccelText, 32, "Alt+");
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
                        fAccelFlags |= FALT; 
                    } 
                    hwndCtl = GetDlgItem(hwndDlg, IDD_SHIFT); 
                    if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1) 
                    {
                        hr = StringCchCat(szAccelText, 32, "Shft+");
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
                        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);
                    numTCHAR = SendMessage(hwndCtl, CB_GETLBTEXTLEN, 
                        nCursel, 0); 
                    if (numTCHAR <= 15)
                        {                   
                        SendMessage(hwndCtl, CB_GETLBTEXT, 
                            nCurSel, (LONG) (LPSTR) szKeyStroke);
                        }
                    else
                        {
                        // TODO: writer error handler
                        }
                         
                    for (i = 0; i < MAXKEYS; i++) 
                    {
                    //
                    // lstrcmp requires that both parameters are
                    // null-terminated.
                    //
                        if(lstrcmp(vkeys[i].pKeyString, szKeyStroke) 
                            == 0) 
                        { 
                            hr = StringCchCopy(szKeyStroke, 16, vkeys[i].pKeyName);
                            if (FAILED(hr))
                            {
                            // TODO: write error handler
                            }
                            break; 
                        } 
                    } 
 
                    // Concatenate the keystroke text to the 
                    // "Ctl+","Alt+", or "Shft+" string. 
 
                        hr = StringCchCat(szAccelText, 32, szKeyStroke);
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
 
                    // Determine the position in the menu of the 
                    // selected menu item. Menu items in the 
                    // "Character" menu have positions 0,2,3, and 4.
                    // Note: the lstrcmp parameters must be
                    // null-terminated. 
 
                    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)/sizeof(TCHAR), 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; i < (UINT) cAccelerators; i++) 
                    { 
                           if (lpaccelNew[i].cmd == (WORD) idItem)
                        {
                            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; 
}