Condividi tramite


Uso degli acceleratori da tastiera

Questa sezione illustra le attività associate agli acceleratori di tastiera.

Uso di una risorsa di tabella acceleratrice

Il modo più comune per aggiungere il supporto dell'acceleratore a un'applicazione consiste nell'includere una risorsa di tabella dell'acceleratore con il file eseguibile dell'applicazione e quindi caricare la risorsa in fase di esecuzione.

In questa sezione vengono illustrati gli argomenti seguenti.

Creazione della risorsa di tabella di accelerazione

È possibile creare una risorsa di tabella dell'acceleratore usando l'istruzione ACCELERATORS nel file di definizione delle risorse dell'applicazione. È necessario assegnare un nome o un identificatore di risorsa alla tabella dell'acceleratore, preferibilmente a differenza di quello di qualsiasi altra risorsa. Il sistema usa questo identificatore per caricare la risorsa in fase di esecuzione.

Ogni acceleratore che definisci richiede un'entrata separata nella tabella degli acceleratori. In ogni voce si definisce la sequenza di tasti (codice carattere ASCII o codice di tasto virtuale) che genera l'acceleratore e l'identificatore dell'acceleratore. È inoltre necessario specificare se la sequenza di tasti deve essere usata in una combinazione con i tasti ALT, MAIUSC o CTRL. Per altre informazioni sui tasti virtuali, vedere Input da tastiera.

Una pressione di tasti ASCII viene specificata sia racchiudendo il carattere ASCII tra virgolette doppie, sia usando il valore intero del carattere in combinazione con il segnale ASCII. Negli esempi seguenti viene illustrato come definire acceleratori ASCII.

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

Una sequenza di tasti di codice della chiave virtuale viene specificata in modo diverso a seconda che la sequenza di tasti sia una chiave alfanumerica o una chiave non alfanumerica. Per una chiave alfanumerica, la lettera o il numero della chiave, racchiusa tra virgolette doppie, viene combinata con il flag VIRTKEY. Per una chiave non alfanumerica, il codice della chiave virtuale per la chiave specifica viene combinato con il flag VIRTKEY. Gli esempi seguenti illustrano come definire acceleratori di codice con chiave virtuale.

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

Nell'esempio seguente viene illustrata una risorsa di tabella dell'acceleratore che definisce gli acceleratori per le operazioni sui file. Il nome della risorsa è 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 si desidera che l'utente preme i tasti ALT, MAIUSC o CTRL in una combinazione con la sequenza di tasti di scelta rapida, specificare i flag ALT, MAIUSC e CONTROL nella definizione dell'acceleratore. Di seguito sono riportati alcuni esempi.

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

Per impostazione predefinita, quando un tasto di scelta rapida corrisponde a una voce di menu, il sistema evidenzia la voce di menu. Puoi usare il flag NOINVERT per impedire l'evidenziazione per un singolo acceleratore. Nell'esempio seguente viene illustrato come usare il flag NOINVERT:

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

Per definire acceleratori che corrispondono alle voci di menu nell'applicazione, includere gli acceleratori nel testo delle voci di menu. Nell'esempio seguente viene illustrato come includere acceleratori nel testo delle voci di menu in un file di definizione delle risorse.

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 

Caricamento della tabella delle risorse dell'acceleratore

Un'applicazione carica una risorsa di tabella dell'acceleratore chiamando la funzione LoadAccelerators e specificando l'handle di istanza dell'applicazione il cui file eseguibile contiene la risorsa e il nome o l'identificatore della risorsa. LoadAccelerators carica la tabella dell'acceleratore specificata in memoria e restituisce l'handle alla tabella dell'acceleratore.

Un'applicazione può caricare una risorsa di tabella dell'acceleratore in qualsiasi momento. In genere, un'applicazione a singolo thread carica la sua tabella degli acceleratori prima di entrare nel suo ciclo principale di messaggi. Un'applicazione che utilizza più thread carica in genere la risorsa della tabella acceleratore per il thread prima di entrare nel ciclo di messaggi per il thread. Un'applicazione o un thread può anche usare più tabelle di acceleratore, ognuna associata a una determinata finestra nell'applicazione. Tale applicazione caricherà la tabella dell'acceleratore per la finestra ogni volta che l'utente ha attivato la finestra. Per ulteriori informazioni sui thread, consultare Processi e thread.

Chiamata della funzione acceleratrice di traduzione

Per gestire gli acceleratori, il ciclo di messaggi di un'applicazione (o del thread) deve contenere una chiamata alla funzione TranslateAccelerator. TranslateAccelerator confronta le sequenze di tasti con una tabella degli accelerator e, se trova una corrispondenza, trasla le sequenze di tasti in un messaggio di WM_COMMAND (o WM_SYSCOMMAND). La funzione invia quindi il messaggio a una routine finestra. I parametri della funzione TranslateAccelerator includono l'handle alla finestra che deve ricevere i messaggi WM_COMMAND, l'handle della tabella di acceleratore usata per convertire gli acceleratori e un puntatore a una struttura MSG contenente un messaggio dalla coda. Nell'esempio seguente viene illustrato come chiamare TranslateAccelerator dall'interno di un ciclo di messaggi.

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

Elaborazione di messaggi WM_COMMAND

Quando si usa un acceleratore, la finestra specificata nella funzione TranslateAccelerator riceve un messaggio WM_COMMAND o WM_SYSCOMMAND. La parola in ordine basso del parametro wParam contiene l'identificatore dell'acceleratore. La procedura della finestra esamina l'identificatore per determinare l'origine del messaggio WM_COMMAND ed elaborare di conseguenza il messaggio.

In genere, se un acceleratore corrisponde a una voce di menu nell'applicazione, all'acceleratore e alla voce di menu viene assegnato lo stesso identificatore. Se è necessario sapere se un messaggio di WM_COMMAND è stato generato da un acceleratore o da una voce di menu, è possibile esaminare la parola ad ordine elevato del parametro wParam. Se un acceleratore ha generato il messaggio, la parola di ordine elevato è 1; se una voce di menu ha generato il messaggio, la parola dell'ordine elevato è 0.

Eliminazione della Tabella Risorsa Acceleratore

Il sistema elimina automaticamente le risorse della tabella degli acceleratori caricate dalla funzione LoadAccelerators, rimuovendo la risorsa dalla memoria dopo la chiusura dell'applicazione.

Creazione di acceleratori per attributi del tipo di carattere

L'esempio in questa sezione illustra come eseguire le attività seguenti:

  • Creare una risorsa di tabella acceleratrice.
  • Carica la tabella dell'acceleratore in fase di esecuzione.
  • Convertire gli acceleratori in un ciclo di messaggi.
  • Elaborare WM_COMMAND messaggi generati dagli acceleratori.

Queste attività vengono illustrate nel contesto di un'applicazione che include un menu carattere e gli acceleratori corrispondenti che consentono all'utente di selezionare gli attributi del tipo di carattere corrente.

La parte seguente di un file di definizione delle risorse definisce il menu carattere e la tabella degli acceleratori associata. Si noti che le voci di menu mostrano le sequenze di tasti di scelta rapida e che ogni acceleratore ha lo stesso identificatore della voce di menu associata.

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

Le sezioni seguenti del file di origine dell'applicazione illustrano come implementare gli acceleratori.

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

Uso di una tabella di accelerazione creata durante l'esecuzione

In questo argomento viene illustrato come usare le tabelle dell'acceleratore create in fase di esecuzione.

Creazione di una tabella acceleratrice Run-Time

Il primo passaggio per la creazione di una tabella acceleratore in fase di esecuzione consiste nel riempire una matrice di strutture ACCEL. Ogni struttura nella matrice definisce un acceleratore nella tabella. La definizione di un acceleratore include i suoi flag, la sua chiave e il suo identificatore. La struttura ACCEL presenta il formato seguente.

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

È possibile definire la sequenza di tasti di un acceleratore specificando un codice carattere ASCII o un codice di chiave virtuale nel tasto membro della strutturaACCEL. Se si specifica un codice di chiave virtuale, è prima necessario includere il flag FVIRTKEY nel membro fVirt; in caso contrario, il sistema interpreta il codice come codice carattere ASCII. È possibile includere i flag FCONTROL, FALT, o FSHIFT, oppure tutti e tre, per combinare il tasto CTRL, ALT o SHIFT con la sequenza di tasti.

Per creare la tabella dell'acceleratore, passare un puntatore alla matrice di strutture ACCEL alla funzione CreateAcceleratorTable. CreateAcceleratorTable crea la tabella dell'acceleratore e restituisce l'handle alla tabella.

Acceleratori di elaborazione

Il processo di caricamento e chiamata degli acceleratori fornito da una tabella acceleratore creata in fase di esecuzione è uguale all'elaborazione di quelle fornite da una risorsa di tabella dell'acceleratore. Per altre informazioni, vedere Caricamento della risorsa della tabella dell'acceleratore, tramite l'elaborazione dei messaggi WM_COMMAND.

Eliminazione di una tabella di accelerazione Run-Time

Il sistema elimina automaticamente le tabelle degli acceleratori create in fase di esecuzione, rimuovendo le risorse dalla memoria dopo la chiusura dell'applicazione. È possibile eliminare definitivamente una tabella dell'acceleratore e rimuoverla dalla memoria precedente passando l'handle della tabella alla funzione DestroyAcceleratorTable.

Creazione di acceleratori modificabili utente

In questo esempio viene illustrato come costruire una finestra di dialogo che consente all'utente di modificare l'acceleratore associato a una voce di menu. La finestra di dialogo è costituita da una casella combinata contenente voci di menu, una casella combinata contenente i nomi dei tasti e le caselle di controllo per la selezione dei tasti CTRL, ALT e MAIUSC. La figura seguente mostra la finestra di dialogo.

finestra di dialogo con caselle combinate e caselle di controllo

Nell'esempio seguente viene illustrato come viene definita la finestra di dialogo nel file di definizione della risorsa.

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

La barra dei menu dell'applicazione contiene un sottomenu carattere i cui elementi hanno acceleratori associati.

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 
}
 

I valori delle voci di menu per il modello di menu sono costanti definite come segue nel file di intestazione dell'applicazione.

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

La finestra di dialogo usa una matrice di strutture VKEY definite dall'applicazione, ognuna contenente una stringa di testo con sequenza di tasti e una stringa di testo dell'acceleratore. Quando viene creata la finestra di dialogo, analizza l'array e aggiunge ogni stringa di testo di sequenza di tasti alla combo box Seleziona sequenza di tasti. Quando l'utente fa clic sul pulsante OK, la finestra di dialogo cerca la stringa di testo del tasto di scelta rapida selezionata e recupera la stringa di testo dell'acceleratore corrispondente. La finestra di dialogo aggiunge la stringa di testo dell'acceleratore al testo della voce di menu selezionata dall'utente. L'esempio seguente mostra la matrice di strutture 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" 
};

La procedura di inizializzazione della finestra di dialogo riempie le caselle combinate Seleziona elemento e Seleziona sequenza di tasti. Dopo che l'utente seleziona una voce di menu e l'acceleratore associato, la finestra di dialogo esamina i controlli nella finestra di dialogo per ottenere la selezione dell'utente, aggiorna il testo della voce di menu e quindi crea una nuova tabella acceleratore contenente il nuovo acceleratore definito dall'utente. Nell'esempio seguente viene illustrata la procedura della finestra di dialogo. Si noti che è necessario inizializzare qualcosa nella procedura della finestra.

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