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

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

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

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

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

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

Ресурс таблицы ускорителей создается с помощью инструкции 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 

В следующем примере показан ресурс accelerator-table, который определяет ускорители для операций с файлами. Имя ресурса — 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 

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

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

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

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

Для обработки ускорителей цикл сообщений приложения (или потока) должен содержать вызов функции 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. На следующем рисунке показано диалоговое окно.

диалоговое окно с полями со списком и проверка

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

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

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

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