Dela via


Använda tangentbordsacceleratorer

Det här avsnittet beskriver uppgifter som är associerade med tangentbordsacceleratorer.

Använda en acceleratortabellresurs

Det vanligaste sättet att lägga till stöd för acceleratorer i ett program är att inkludera en acceleratortabellresurs med programmets körbara fil och sedan läsa in resursen vid körning.

Det här avsnittet täcker följande ämnen.

Skapa acceleratortabellresursen

Du skapar en acceleratortabellresurs med hjälp av ACCELERATORS--instruktionen i programmets resursdefinitionsfil. Du måste tilldela ett namn eller resursidentifierare till acceleratortabellen, helst till skillnad från någon annan resurs. Systemet använder den här identifieraren för att läsa in resursen under körning.

Varje accelerator som du definierar kräver en separat post i acceleratortabellen. I varje post definierar du tangenttryckningen (antingen en ASCII-teckenkod eller virtuell nyckelkod) som genererar acceleratorn och acceleratorns identifierare. Du måste också ange om tangenttryckningen måste användas i någon kombination med ALT-, SKIFT- eller CTRL-tangenterna. Mer information om virtuella nycklar finns i Tangentbordsindata.

En ASCII-tangenttryckning anges antingen genom att ASCII-tecknet omges av dubbla citattecken eller genom att använda heltalsvärdet för tecknet i kombination med ASCII-flaggan. I följande exempel visas hur du definierar ASCII-acceleratorer.

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

En tangenttryckning för virtuell nyckel anges på olika sätt beroende på om tangenttryckningen är en alfanumerisk nyckel eller en icke-alfanumerisk nyckel. För en alfanumerisk nyckel kombineras nyckelns bokstav eller nummer, omgivet av dubbla citattecken, med flaggan VIRTKEY. För en icke-alfanumerisk nyckel kombineras den virtuella nyckelkoden för den specifika nyckeln med flaggan VIRTKEY. I följande exempel visas hur du definierar kodacceleratorer med virtuella nycklar.

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

I följande exempel visas en resurs för snabbkommandotabell som definierar snabbkommandon för filåtgärder. Namnet på resursen är 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 

Om du vill att användaren ska trycka på ALT-, SKIFT- eller CTRL-tangenterna i någon kombination med acceleratorns tangenttryckning anger du ALT-, SKIFT- och CONTROL-flaggorna i acceleratorns definition. Här följer några exempel.

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

När en acceleratornyckel som standard motsvarar ett menyalternativ markerar systemet menyalternativet. Du kan använda flaggan NOINVERT för att förhindra markering för en enskild accelerator. I följande exempel visas hur du använder flaggan NOINVERT:

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

Om du vill definiera acceleratorer som motsvarar menyalternativ i ditt program inkluderar du acceleratorerna i texten i menyalternativen. I följande exempel visas hur du inkluderar acceleratorer i menyobjekttext i en resursdefinitionsfil.

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 

Laddar resurs för acceleratortabell

Ett program läser in en acceleratortabellresurs genom att anropa funktionen LoadAccelerators och ange instanshandtaget för programmet vars körbara fil innehåller resursen och resursens namn eller identifierare. LoadAccelerators läser in den angivna acceleratortabellen i minnet och returnerar handtaget till acceleratortabellen.

Ett program kan när som helst läsa in en acceleratortabellresurs. Vanligtvis läser ett entrådat program in sin acceleratortabell innan det anger huvudmeddelandeloopen. Ett program som använder flera trådar läser vanligtvis in acceleratortabellresursen för en tråd innan den går in i meddelandeloopen för tråden. Ett program eller en tråd kan också använda flera acceleratortabeller, var och en associerad med ett visst fönster i programmet. Ett sådant program skulle läsa in acceleratortabellen för fönstret varje gång användaren aktiverade fönstret. Mer information om trådar finns i Processer och trådar.

Anropa Translate Accelerator-funktionen

För att bearbeta acceleratorer måste ett programs (eller trådens) meddelandeloop innehålla ett anrop till funktionen TranslateAccelerator. TranslateAccelerator jämför tangenttryckningar med en acceleratortabell och, om den hittar en matchning, översätter tangenttryckningarna till ett WM_COMMAND -meddelande (eller WM_SYSCOMMAND). Funktionen skickar sedan meddelandet till en fönsterprocedur. Parametrarna för funktionen TranslateAccelerator inkluderar handtaget till fönstret som ska ta emot de WM_COMMAND meddelanden, handtaget till acceleratortabellen som används för att översätta acceleratorer och en pekare till en MSG struktur som innehåller ett meddelande från kön. I följande exempel visas hur du anropar TranslateAccelerator inifrån en meddelandeloop.

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

Bearbeta WM_COMMAND meddelanden

När en accelerator används får fönstret som anges i funktionen TranslateAccelerator ett WM_COMMAND- eller WM_SYSCOMMAND-meddelande. Lågordningsordet i parametern wParam innehåller acceleratorns identifierare. Fönsterproceduren undersöker identifieraren för att fastställa källan till det WM_COMMAND meddelandet och bearbeta meddelandet därefter.

Om en accelerator vanligtvis motsvarar ett menyalternativ i programmet tilldelas acceleratorn och menyalternativet samma identifierare. Om du behöver veta om ett WM_COMMAND meddelande har genererats av en accelerator eller av ett menyalternativ, kan du granska den högre ordningen av ordet i wParam parametern. Om en accelerator genererade meddelandet är högordningsordet 1. Om ett menyalternativ genererade meddelandet är det högordade ordet 0.

Förstöra acceleratorns tabellresurs

Systemet förstör automatiskt acceleratortabellresurser som läses in av funktionen LoadAccelerators och tar bort resursen från minnet när programmet har stängts.

Skapa acceleratorer för teckensnittsattribut

Exemplet i det här avsnittet visar hur du utför följande uppgifter:

  • Skapa en resurs för acceleratortabell.
  • Läs in acceleratortabellen vid körning.
  • Översätt acceleratorer i en meddelandeloop.
  • Bearbeta WM_COMMAND meddelanden som genereras av acceleratorerna.

Dessa uppgifter demonstreras i kontexten av en applikation som innehåller en Tecken--meny och tillhörande snabbkommandon som tillåter användaren att välja attribut för det aktuella teckensnittet.

Följande del av en resursdefinitionsfil definierar menyn Tecken och den associerade acceleratortabellen. Observera att menyalternativen visar tangenttryckningar för acceleratorn och att varje accelerator har samma identifierare som dess associerade menyalternativ.

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

Följande avsnitt i programmets källfil visar hur du implementerar acceleratorerna.

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

Använda en acceleratortabell som skapats vid körningstid

I det här avsnittet beskrivs hur du använder acceleratortabeller som skapats vid körning.

Skapa en Run-Time-acceleratortabell

Det första steget när du skapar en acceleratortabell vid körningstid är att fylla i en matris med ACCEL- strukturer. Varje struktur i matrisen definierar en accelerator i tabellen. En acceleratordefinition innehåller dess flaggor, dess nyckel och dess identifierare. Den ACCEL- strukturen har följande formulär.

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

Du definierar en accelerators tangenttryckning genom att ange en ASCII-teckenkod eller en virtuell nyckelkod i nyckel medlem i ACCEL- struktur. Om du anger en kod för virtuell nyckel måste du först inkludera flaggan FVIRTKEY i fVirt medlem. annars tolkar systemet koden som en ASCII-teckenkod. Du kan inkludera flaggan FCONTROL, FALTeller FSHIFT eller alla tre för att kombinera CTRL-, ALT- eller SKIFT-tangenten med tangenttryckningen.

Om du vill skapa acceleratortabellen skickar du en pekare till matrisen med ACCEL- strukturer till funktionen CreateAcceleratorTable. CreateAcceleratorTable skapar acceleratortabellen och returnerar handtaget till tabellen.

Bearbetningsacceleratorer

Processen med att ladda och anropa acceleratorer som tillhandahålls av en acceleratortabell som skapas i körningstid är densamma som att bearbeta de som tillhandahålls av en acceleratortabellresurs. Mer information finns i Läsa in acceleratortabellresursen fram till Bearbeta WM_COMMAND-meddelanden.

Förstöra en Run-Time accelerator-tabell

Systemet förstör automatiskt acceleratortabeller som skapas vid körning, vilket tar bort resurserna från minnet när programmet har stängts. Du kan förstöra en acceleratortabell och ta bort den från minnet tidigare genom att skicka tabellens handtag till funktionen DestroyAcceleratorTable.

Skapa acceleratorer som kan redigeras av användare

Det här exemplet visar hur du skapar en dialogruta som gör att användaren kan ändra acceleratorn som är associerad med ett menyalternativ. Dialogrutan består av en kombinationsruta som innehåller menyalternativ, en kombinationsruta som innehåller namnen på nycklar och kryssrutor för att markera TANGENTERNA CTRL, ALT och SKIFT. Följande bild visar dialogrutan.

dialogruta med kombinationsrutor och kryssrutor

I följande exempel visas hur dialogrutan definieras i resursdefinitionsfilen.

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

Programmets menyrad innehåller en Tecken undermeny vars objekt har acceleratorer associerade med dem.

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 
}
 

Menyalternativets värden för menymallen är konstanter som definieras enligt följande i programmets huvudfil.

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

I dialogrutan används en matris med programdefinierade VKEY-strukturer som var och en innehåller en tangenttryckningstextsträng och en acceleratortextsträng. När dialogrutan skapas parsar den matrisen och lägger till varje tangenttryckningstextsträng i kombinationsrutan Välj tangenttryckning. När användaren klickar på knappen OK letar dialogrutan upp den markerade tangenttryckningstextsträngen och hämtar motsvarande acceleratortextsträng. Dialogrutan lägger till acceleratortextsträngen till texten i menyalternativet som användaren har valt. I följande exempel visas matrisen med VKEY-strukturer:

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

Dialogrutans initieringsprocedur fyller kombinationsrutorna Välj objekt och Välj tangenttryckning. När användaren har valt ett menyalternativ och tillhörande accelerator undersöker dialogrutan kontrollerna i dialogrutan för att hämta användarens val, uppdaterar texten i menyalternativet och skapar sedan en ny acceleratortabell som innehåller den användardefinierade nya acceleratorn. I följande exempel visas dialogrutans procedur. Observera att du måste initiera i fönsterproceduren.

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