Compartilhar via


Como usar aceleradores de teclado

Esta seção aborda as tarefas associadas aos aceleradores de teclado.

Usando um recurso de tabela de acelerador

A maneira mais comum de adicionar suporte de acelerador a um aplicativo é incluir um recurso de tabela de acelerador com o arquivo executável do aplicativo e carregar o recurso em tempo de execução.

Esta seção aborda os tópicos a seguir.

Criando o recurso tabela acelerador

Você cria um recurso de tabela de acelerador usando a instrução ACCELERATORS no arquivo de definição de recursos do aplicativo. Você deve atribuir um nome ou identificador de recurso à tabela de aceleradores, preferencialmente diferente daquele de qualquer outro recurso. O sistema usa esse identificador para carregar o recurso em tempo de execução.

Cada acelerador definido requer uma entrada separada na tabela de aceleradores. Em cada entrada, você define o pressionamento de tecla (um código de caractere ASCII ou um código de chave virtual) que gera o acelerador e o identificador do acelerador. Você também deve especificar se o pressionamento de teclas deve ser usado em alguma combinação com as teclas ALT, SHIFT ou CTRL. Para obter mais informações sobre teclas virtuais, consulte Entrada de teclado.

Um pressionamento de tecla ASCII é especificado colocando o caractere ASCII entre aspas duplas ou usando o valor inteiro do caractere em combinação com o sinalizador ASCII. Os exemplos a seguir mostram como definir aceleradores ASCII.

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

Um pressionamento de tecla de código de chave virtual é especificado de forma diferente dependendo se o pressionamento de tecla é uma chave alfanumérica ou uma chave não alfanumérica. Para uma chave alfanumérica, a letra ou o número da chave, entre aspas duplas, é combinado com o sinalizador VIRTKEY . Para uma chave não alfanumérica, o código de chave virtual para a chave específica é combinado com o sinalizador VIRTKEY . Os exemplos a seguir mostram como definir aceleradores de código de chave virtual.

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

O exemplo a seguir mostra um recurso de tabela de aceleradores que define aceleradores para operações de arquivo. O nome do recurso é 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 

Se você quiser que o usuário pressione as teclas ALT, SHIFT ou CTRL em alguma combinação com o pressionamento de teclas de acelerador, especifique os sinalizadores ALT, SHIFT e CONTROL na definição do acelerador. Estes são alguns exemplos:

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

Por padrão, quando uma tecla aceleradora corresponde a um item de menu, o sistema realça o item de menu. Você pode usar o sinalizador NOINVERT para impedir o realce de um acelerador individual. O exemplo a seguir mostra como usar o sinalizador NOINVERT :

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

Para definir aceleradores que correspondem aos itens de menu em seu aplicativo, inclua os aceleradores no texto dos itens de menu. O exemplo a seguir mostra como incluir aceleradores no texto do item de menu em um arquivo de definição de recurso.

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 

Carregando o recurso tabela acelerador

Um aplicativo carrega um recurso de tabela de acelerador chamando a função LoadAccelerators e especificando o identificador de instância para o aplicativo cujo arquivo executável contém o recurso e o nome ou identificador do recurso. LoadAccelerators carrega a tabela de aceleradores especificada na memória e retorna o identificador para a tabela de aceleradores.

Um aplicativo pode carregar um recurso de tabela de acelerador a qualquer momento. Normalmente, um aplicativo de thread único carrega sua tabela de aceleradores antes de inserir seu loop de mensagem main. Um aplicativo que usa vários threads normalmente carrega o recurso accelerator-table para um thread antes de inserir o loop de mensagem para o thread. Um aplicativo ou thread também pode usar várias tabelas de acelerador, cada uma associada a uma janela específica no aplicativo. Esse aplicativo carregaria a tabela de aceleradores para a janela sempre que o usuário ativasse a janela. Para obter mais informações sobre threads, consulte Processos e Threads.

Chamando a função Traduzir Acelerador

Para processar aceleradores, o loop de mensagem de um aplicativo (ou thread) deve conter uma chamada para a função TranslateAccelerator . TranslateAccelerator compara pressionamentos de tecla a uma tabela de aceleradores e, se encontrar uma correspondência, converte os pressionamentos de tecla em uma mensagem de WM_COMMAND (ou WM_SYSCOMMAND). Em seguida, a função envia a mensagem para um procedimento de janela. Os parâmetros da função TranslateAccelerator incluem o identificador para a janela que deve receber as mensagens WM_COMMAND , o identificador para a tabela de aceleradores usada para traduzir aceleradores e um ponteiro para uma estrutura MSG que contém uma mensagem da fila. O exemplo a seguir mostra como chamar TranslateAccelerator de dentro de um loop de mensagem.

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

Processando mensagens de WM_COMMAND

Quando um acelerador é usado, a janela especificada na função TranslateAccelerator recebe uma mensagem WM_COMMAND ou WM_SYSCOMMAND . A palavra de baixa ordem do parâmetro wParam contém o identificador do acelerador. O procedimento de janela examina o identificador para determinar a origem da mensagem WM_COMMAND e processar a mensagem adequadamente.

Normalmente, se um acelerador corresponder a um item de menu no aplicativo, o acelerador e o item de menu receberão o mesmo identificador. Se você precisar saber se uma mensagem WM_COMMAND foi gerada por um acelerador ou por um item de menu, você pode examinar a palavra de alta ordem do parâmetro wParam . Se um acelerador gerou a mensagem, a palavra de alta ordem será 1; se um item de menu gerou a mensagem, a palavra de alta ordem será 0.

Destruindo o recurso tabela acelerador

O sistema destrói automaticamente os recursos de tabela de acelerador carregados pela função LoadAccelerators , removendo o recurso da memória após o fechamento do aplicativo.

Criando aceleradores para atributos de fonte

O exemplo nesta seção mostra como executar as seguintes tarefas:

  • Criar um recurso de tabela de acelerador.
  • Carregue a tabela de aceleradores em tempo de execução.
  • Traduzir aceleradores em um loop de mensagem.
  • Processar WM_COMMAND mensagens geradas pelos aceleradores.

Essas tarefas são demonstradas no contexto de um aplicativo que inclui um menu Caractere e aceleradores correspondentes que permitem ao usuário selecionar atributos da fonte atual.

A parte a seguir de um arquivo de definição de recurso define o menu Caractere e a tabela de acelerador associada. Observe que os itens de menu mostram os pressionamentos de teclas de acelerador e que cada acelerador tem o mesmo identificador que seu item de menu associado.

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

As seções a seguir do arquivo de origem do aplicativo mostram como implementar os aceleradores.

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

Usando uma tabela aceleradora criada em tempo de execução

Este tópico discute como usar tabelas de acelerador criadas em tempo de execução.

Criando uma tabela de acelerador de Run-Time

A primeira etapa na criação de uma tabela de aceleradores em tempo de execução é preencher uma matriz de estruturas ACCEL . Cada estrutura na matriz define um acelerador na tabela. A definição de um acelerador inclui seus sinalizadores, sua chave e seu identificador. A estrutura ACCEL tem o seguinte formulário.

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

Defina o pressionamento de teclas de um acelerador especificando um código de caractere ASCII ou um código de chave virtual no membro de chave da estrutura ACCEL . Se você especificar um código de chave virtual, deverá primeiro incluir o sinalizador FVIRTKEY no membro fVirt ; caso contrário, o sistema interpreta o código como um código de caractere ASCII. Você pode incluir o sinalizador FCONTROL, FALT ou FSHIFT , ou todos os três, para combinar a tecla CTRL, ALT ou SHIFT com o pressionamento de tecla.

Para criar a tabela de aceleradores, passe um ponteiro para a matriz de estruturas ACCEL para a função CreateAcceleratorTable . CreateAcceleratorTable cria a tabela de aceleradores e retorna o identificador para a tabela.

Aceleradores de processamento

O processo de carregar e chamar aceleradores fornecidos por uma tabela de aceleradores criada em tempo de execução é o mesmo que processar aqueles fornecidos por um recurso de tabela de aceleradores. Para obter mais informações, consulte Carregando o recurso de tabela de aceleradores por meio do processamento de mensagens WM_COMMAND.

Destruindo uma tabela de acelerador de Run-Time

O sistema destrói automaticamente as tabelas de acelerador criadas em tempo de execução, removendo os recursos da memória após o fechamento do aplicativo. Você pode destruir uma tabela de aceleradores e removê-la da memória anteriormente passando o identificador da tabela para a função DestroyAcceleratorTable .

Criando aceleradores editáveis pelo usuário

Este exemplo mostra como construir uma caixa de diálogo que permite que o usuário altere o acelerador associado a um item de menu. A caixa de diálogo consiste em uma caixa de combinação que contém itens de menu, uma caixa de combinação que contém os nomes das teclas e marcar caixas para selecionar as teclas CTRL, ALT e SHIFT. A ilustração a seguir mostra a caixa de diálogo.

caixa de diálogo com caixas de combinação e caixas de marcar

O exemplo a seguir mostra como a caixa de diálogo é definida no arquivo de definição de recurso.

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

A barra de menus do aplicativo contém um submenu Character cujos itens têm aceleradores associados a eles.

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 
}
 

Os valores de item de menu para o modelo de menu são constantes definidas da seguinte maneira no arquivo de cabeçalho do aplicativo.

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

A caixa de diálogo usa uma matriz de estruturas VKEY definidas pelo aplicativo, cada uma contendo uma cadeia de caracteres de texto de pressionamento de tecla e uma cadeia de caracteres de texto acelerador. Quando a caixa de diálogo é criada, ela analisa a matriz e adiciona cada cadeia de caracteres de texto de pressionamento de teclas à caixa de combinação Selecionar Pressionamento de Tecla . Quando o usuário clica no botão OK , a caixa de diálogo pesquisa a cadeia de caracteres de texto de pressionamento de teclas selecionada e recupera a cadeia de caracteres de texto do acelerador correspondente. A caixa de diálogo acrescenta a cadeia de caracteres de texto do acelerador ao texto do item de menu selecionado pelo usuário. O exemplo a seguir mostra a matriz de estruturas 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" 
};

O procedimento de inicialização da caixa de diálogo preenche as caixas de combinação Selecionar Item e Selecionar Pressionamento de Tecla . Depois que o usuário seleciona um item de menu e um acelerador associado, a caixa de diálogo examina os controles na caixa de diálogo para obter a seleção do usuário, atualiza o texto do item de menu e cria uma nova tabela de aceleradores que contém o novo acelerador definido pelo usuário. O exemplo a seguir mostra o procedimento da caixa de diálogo. Observe que você deve inicializar em seu procedimento de janela.

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