使用鍵盤快速鍵

本節涵蓋與鍵盤快速鍵相關聯的工作。

使用快速鍵資料表資源

將加速器支援新增至應用程式最常見的方式,就是將快速鍵資料表資源包含在應用程式的可執行檔中,然後在執行時間載入資源。

本節涵蓋下列主題。

建立快速鍵資料表資源

您可以在應用程式的資源定義檔案中使用 ACCELERATORS 語句來建立快速鍵資料表資源。 您必須將名稱或資源識別碼指派給快速鍵資料表,最好與任何其他資源的名稱或資源識別碼不同。 系統會使用此識別碼在執行時間載入資源。

您定義的每個加速器都需要快速鍵資料表中的個別專案。 在每個專案中,您會定義 ASCII 字元碼或虛擬按鍵代碼 (產生快速鍵和快速鍵識別碼的虛擬按鍵程式碼) 。 您也必須指定按鍵是否必須與 ALT、SHIFT 或 CTRL 鍵組合使用。 如需虛擬按鍵的詳細資訊,請參閱 鍵盤輸入

以雙引號括住 ASCII 字元,或使用字元的整數值搭配 ASCII 旗標來指定 ASCII 按鍵。 下列範例示範如何定義 ASCII 加速器。

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

虛擬按鍵程式碼按鍵的指定方式會根據擊鍵是英數位元鍵或非英數位元鍵而有所不同。 對於英數位元索引鍵,金鑰的字母或數位會以雙引號括住,會與 VIRTKEY 旗標結合。 對於非英數位元索引鍵,特定金鑰的虛擬金鑰碼會與 VIRTKEY 旗標結合。 下列範例示範如何定義虛擬索引鍵程式碼加速器。

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

下列範例顯示可定義檔案作業加速器的快速鍵資料表資源。 資源的名稱是 FileAccel

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

如果您想要讓使用者按 ALT、SHIFT 或 CTRL 鍵搭配快速鍵按鍵組合,請在快速鍵的定義中指定 ALT、SHIFT 和 CONTROL 旗標。 以下有一些範例。

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

根據預設,當快速鍵對應至功能表項目時,系統會醒目提示功能表項目。 您可以使用 NOINVERT 旗標來防止個別加速器的醒目提示。 下列範例示範如何使用 NOINVERT 旗標:

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

若要定義對應至應用程式中功能表項目的快速鍵,請在功能表項目的文字中包含快速鍵。 下列範例示範如何在資源定義檔的功能表項目文字中包含快速鍵。

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

載入快速鍵資料表資源

應用程式會呼叫 LoadAccelerators 函式,並指定可執行檔包含資源和資源名稱或識別碼的應用程式實例控制碼,以載入快速鍵資料表資源。 LoadAccelerators 會將 指定的快速鍵資料表載入記憶體中,並將控制碼傳回至快速鍵資料表。

應用程式可以隨時載入快速鍵資料表資源。 通常,單一執行緒應用程式會在進入主要訊息迴圈之前載入其快速鍵資料表。 使用多個執行緒的應用程式通常會先載入執行緒的快速鍵資料表資源,再輸入執行緒的訊息迴圈。 應用程式或執行緒也可能使用多個快速鍵資料表,每個資料表都與應用程式中的特定視窗相關聯。 這類應用程式會在每次使用者啟動視窗時載入視窗的快速鍵資料表。 如需執行緒的詳細資訊,請參閱 進程和執行緒

呼叫翻譯加速器函式

若要處理加速器,應用程式的 (或執行緒的) 訊息迴圈必須包含 TranslateAccelerator 函式的呼叫。 TranslateAccelerator 會將擊鍵與快速鍵資料表進行比較,如果找到相符專案,請將擊鍵轉譯成 WM_COMMAND (或 WM_SYSCOMMAND) 訊息。 函式接著會將訊息傳送至視窗程式。 TranslateAccelerator函式的參數包括接收WM_COMMAND訊息之視窗的控制碼、用來翻譯快速鍵的快速鍵資料表控制碼,以及包含來自佇列之訊息的MSG結構的指標。 下列範例示範如何從訊息迴圈內呼叫 TranslateAccelerator

MSG msg;
BOOL bRet;

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

處理WM_COMMAND訊息

使用快速鍵時, TranslateAccelerator 函式中指定的視窗會收到 WM_COMMANDWM_SYSCOMMAND 訊息。 wParam參數的低序字組包含加速器的識別碼。 視窗程式會檢查識別碼,以判斷 WM_COMMAND 訊息的來源,並據以處理訊息。

一般而言,如果快速鍵對應至應用程式中的功能表項目,則快速鍵和功能表項目會指派相同的識別碼。 如果您需要知道快速鍵或功能表項目產生 WM_COMMAND 訊息,您可以檢查 wParam 參數的高序單字。 如果快速鍵產生訊息,則高序字為 1;如果功能表項目產生訊息,則高序單字為 0。

終結快速鍵資料表資源

系統會自動終結 LoadAccelerators 函式所載入的快速鍵資料表資源,並在應用程式關閉之後從記憶體中移除資源。

建立字型屬性的快速鍵

本節中的範例示範如何執行下列工作:

  • 建立快速鍵資料表資源。
  • 在執行時間載入快速鍵資料表。
  • 轉譯訊息迴圈中的快速鍵。
  • 處理 WM_COMMAND 加速器所產生的訊息。

這些工作會在應用程式的內容中示範,其中包含 字元 功能表和對應的快速鍵,讓使用者選取目前字型的屬性。

資源定義檔案的下列部分會定義 [字元 ] 功能表和相關聯的快速鍵資料表。 請注意,功能表項目會顯示快速鍵按鍵,而且每個快速鍵都有與其相關聯功能表項目相同的識別碼。

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

應用程式來源檔案的下列各節示範如何實作加速器。

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

使用在執行時間建立的快速鍵資料表

本主題討論如何使用在執行時間建立的快速鍵資料表。

建立Run-Time快速鍵資料表

在執行時間建立快速鍵資料表的第一個步驟是填滿 ACCEL 結構的陣列。 陣列中的每個結構都會定義資料表中的快速鍵。 加速器的定義包括其旗標、索引鍵和識別碼。 ACCEL結構具有下列形式。

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

您可以在ACCEL結構的按鍵成員中指定 ASCII 字元碼或虛擬按鍵程式碼,以定義快速鍵的按鍵。 如果您指定虛擬金鑰程式碼,您必須先在fVirt成員中包含FVIRTKEY旗標;否則,系統會將程式碼解譯為 ASCII 字元碼。 您可以包含 FCONTROLFALTFSHIFT 旗標,或這三個旗標,以結合 CTRL、ALT 或 SHIFT 鍵與按鍵。

若要建立快速鍵資料表,請將 ACCEL 結構的指標傳遞至 CreateAcceleratorTable 函式。 CreateAcceleratorTable 會建立快速鍵資料表,並將控制碼傳回至資料表。

處理加速器

載入和呼叫在執行時間建立之快速鍵資料表所提供的快速鍵程式,與處理快速鍵資料表資源所提供的相同。 如需詳細資訊,請參閱透過處理WM_COMMAND訊息載入快速鍵資料表資源

終結Run-Time快速鍵資料表

系統會自動終結在執行時間建立的快速鍵資料表,並在應用程式關閉之後從記憶體中移除資源。 您可以將資料表的控制碼傳遞至 DestroyAcceleratorTable 函式,以終結快速鍵資料表,並從先前的記憶體中移除它。

建立使用者可編輯加速器

此範例示範如何建構對話方塊,讓使用者能夠變更與功能表項目相關聯的快速鍵。 對話方塊包含包含功能表項目的下拉式方塊、包含按鍵名稱的下拉式方塊,以及選取 CTRL、ALT 和 SHIFT 鍵的核取方塊。 下圖顯示對話方塊。

具有下拉式方塊和核取方塊的對話方塊

下列範例示範如何在資源定義檔中定義對話方塊。

EdAccelBox DIALOG 5, 17, 193, 114 
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION 
CAPTION "Edit Accelerators" 
BEGIN 
    COMBOBOX        IDD_MENUITEMS, 10, 22, 52, 53, 
                        CBS_SIMPLE | CBS_SORT | WS_VSCROLL | 
                        WS_TABSTOP 
    CONTROL         "Control", IDD_CNTRL, "Button", 
                        BS_AUTOCHECKBOX | WS_TABSTOP, 
                        76, 35, 40, 10 
    CONTROL         "Alt", IDD_ALT, "Button", 
                        BS_AUTOCHECKBOX | WS_TABSTOP, 
                        76, 48, 40, 10 
    CONTROL         "Shift", IDD_SHIFT, "Button", 
                        BS_AUTOCHECKBOX | WS_TABSTOP, 
                        76, 61, 40, 10 
    COMBOBOX        IDD_KEYSTROKES, 124, 22, 58, 58, 
                        CBS_SIMPLE | CBS_SORT | WS_VSCROLL | 
                        WS_TABSTOP 
    PUSHBUTTON      "Ok", IDOK, 43, 92, 40, 14 
    PUSHBUTTON      "Cancel", IDCANCEL, 103, 92, 40, 14 
    LTEXT           "Select Item:", 101, 10, 12, 43, 8 
    LTEXT           "Select Keystroke:", 102, 123, 12, 60, 8 
END

應用程式的功能表列包含 字元 子功能表,其專案具有與其相關聯的快速鍵。

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

功能表範本的功能表項目值是應用程式標頭檔中定義的常數,如下所示。

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

對話方塊會使用應用程式定義的 VKEY 結構的陣列,每個結構都包含按鍵文字字串和快速鍵文字字串。 建立對話方塊時,它會剖析陣列,並將每個按鍵文字字串新增至 [選取按鍵 ] 下拉式方塊。 當使用者按一下 [ 確定 ] 按鈕時,對話方塊會查閱選取的按鍵文字字串,並擷取對應的快速鍵文字字串。 對話方塊會將快速鍵文字字串附加至使用者選取的功能表項目文字。 下列範例顯示 VKEY 結構的陣列:

// VKey Lookup Support 
 
#define MAXKEYS 25 
 
typedef struct _VKEYS { 
    char *pKeyName; 
    char *pKeyString; 
} VKEYS; 
 
VKEYS vkeys[MAXKEYS] = { 
    "BkSp",     "Back Space", 
    "PgUp",     "Page Up", 
    "PgDn",     "Page Down", 
    "End",      "End", 
    "Home",     "Home", 
    "Lft",      "Left", 
    "Up",       "Up", 
    "Rgt",      "Right", 
    "Dn",       "Down", 
    "Ins",      "Insert", 
    "Del",      "Delete", 
    "Mult",     "Multiply", 
    "Add",      "Add", 
    "Sub",      "Subtract", 
    "DecPt",    "Decimal Point", 
    "Div",      "Divide", 
    "F2",       "F2", 
    "F3",       "F3", 
    "F5",       "F5", 
    "F6",       "F6", 
    "F7",       "F7", 
    "F8",       "F8", 
    "F9",       "F9", 
    "F11",      "F11", 
    "F12",      "F12" 
};

對話方塊的初始化程式會填入 [選取專案 ] 和 [ 選取按鍵 ] 下拉式方塊。 在使用者選取功能表項目和相關聯的快速鍵之後,對話方塊會檢查對話方塊中的控制項,以取得使用者的選取範圍、更新功能表項目的文字,然後建立包含使用者定義新快速鍵的新快速鍵資料表。 下列範例顯示對話方塊程式。 請注意,您必須在視窗程式中初始化。

// Global variables 
 
HWND hwndMain;      // handle to main window 
HACCEL haccel;      // handle to accelerator table 
 
// Dialog-box procedure 
 
BOOL CALLBACK EdAccelProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
    int nCurSel;            // index of list box item 
    UINT idItem;            // menu-item identifier 
    UINT uItemPos;          // menu-item position 
    UINT i, j = 0;          // loop counters 
    static UINT cItems;     // count of items in menu 
    char szTemp[32];        // temporary buffer 
    char szAccelText[32];   // buffer for accelerator text 
    char szKeyStroke[16];   // buffer for keystroke text 
    static char szItem[32]; // buffer for menu-item text 
    HWND hwndCtl;           // handle to control window 
    static HMENU hmenu;     // handle to "Character" menu 
    PCHAR pch, pch2;        // pointers for string copying 
    WORD wVKCode;           // accelerator virtual-key code 
    BYTE fAccelFlags;       // fVirt flags for ACCEL structure 
    LPACCEL lpaccelNew;     // pointer to new accelerator table 
    HACCEL haccelOld;       // handle to old accelerator table 
    int cAccelerators;      // number of accelerators in table 
    static BOOL fItemSelected = FALSE; // item selection flag 
    static BOOL fKeySelected = FALSE;  // key selection flag 
    HRESULT hr;
    INT numTCHAR;           // TCHARs in listbox text
 
    switch (uMsg) 
    { 
        case WM_INITDIALOG: 
 
            // Get the handle to the menu-item combo box. 
 
            hwndCtl = GetDlgItem(hwndDlg, IDD_MENUITEMS); 
 
            // Get the handle to the Character submenu and
            // count the number of items it has. In this example, 
            // the menu has position 0. You must alter this value 
            // if you add additional menus. 
            hmenu = GetSubMenu(GetMenu(hwndMain), 0); 
            cItems = GetMenuItemCount(hmenu); 
 
            // Get the text of each item, strip out the '&' and 
            // the accelerator text, and add the text to the 
            // menu-item combo box. 
 
            for (i = 0; i < cItems; i++) 
            { 
                if (!(GetMenuString(hmenu, i, szTemp, 
                        sizeof(szTemp)/sizeof(TCHAR), MF_BYPOSITION))) 
                    continue; 
                for (pch = szTemp, pch2 = szItem; *pch != '\0'; ) 
                { 
                    if (*pch != '&') 
                    { 
                        if (*pch == '\t') 
                        { 
                            *pch = '\0'; 
                            *pch2 = '\0'; 
                        } 
                        else *pch2++ = *pch++; 
                    } 
                    else pch++; 
                } 
                SendMessage(hwndCtl, CB_ADDSTRING, 0, 
                    (LONG) (LPSTR) szItem); 
            } 
 
            // Now fill the keystroke combo box with the list of 
            // keystrokes that will be allowed for accelerators. 
            // The list of keystrokes is in the application-defined 
            // structure called "vkeys". 
 
            hwndCtl = GetDlgItem(hwndDlg, IDD_KEYSTROKES); 
            for (i = 0; i < MAXKEYS; i++) 
            {
                SendMessage(hwndCtl, CB_ADDSTRING, 0, 
                    (LONG) (LPSTR) vkeys[i].pKeyString); 
            }
 
            return TRUE; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                case IDD_MENUITEMS: 
 
                    // The user must select an item from the combo 
                    // box. This flag is checked during IDOK
                    // processing to be sure a selection was made. 
 
                    fItemSelected = TRUE; 
                    return 0; 
 
                case IDD_KEYSTROKES: 
 
                    // The user must select an item from the combo
                    // box. This flag is checked during IDOK
                    // processing to be sure a selection was made. 
 
                    fKeySelected = TRUE; 
 
                    return 0; 
 
                case IDOK: 
 
                    // If the user has not selected a menu item 
                    // and a keystroke, display a reminder in a 
                    // message box. 
 
                    if (!fItemSelected || !fKeySelected) 
                    { 
                        MessageBox(hwndDlg, 
                            "Item or key not selected.", NULL, 
                            MB_OK); 
                        return 0; 
                    } 
 
                    // Determine whether the CTRL, ALT, and SHIFT 
                    // keys are selected. Concatenate the 
                    // appropriate strings to the accelerator- 
                    // text buffer, and set the appropriate 
                    // accelerator flags. 
 
                    szAccelText[0] = '\0'; 
                    hwndCtl = GetDlgItem(hwndDlg, IDD_CNTRL); 
                    if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1) 
                    { 
                        hr = StringCchCat(szAccelText, 32, "Ctl+");
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
                        fAccelFlags |= FCONTROL; 
                    } 
                    hwndCtl = GetDlgItem(hwndDlg, IDD_ALT); 
                    if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1) 
                    { 
                        hr = StringCchCat(szAccelText, 32, "Alt+");
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
                        fAccelFlags |= FALT; 
                    } 
                    hwndCtl = GetDlgItem(hwndDlg, IDD_SHIFT); 
                    if (SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == 1) 
                    {
                        hr = StringCchCat(szAccelText, 32, "Shft+");
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
                        fAccelFlags |= FSHIFT; 
                    } 
 
                    // Get the selected keystroke, and look up the 
                    // accelerator text and the virtual-key code 
                    // for the keystroke in the vkeys structure. 
 
                    hwndCtl = GetDlgItem(hwndDlg, IDD_KEYSTROKES); 
                    nCurSel = (int) SendMessage(hwndCtl, 
                        CB_GETCURSEL, 0, 0);
                    numTCHAR = SendMessage(hwndCtl, CB_GETLBTEXTLEN, 
                        nCursel, 0); 
                    if (numTCHAR <= 15)
                        {                   
                        SendMessage(hwndCtl, CB_GETLBTEXT, 
                            nCurSel, (LONG) (LPSTR) szKeyStroke);
                        }
                    else
                        {
                        // TODO: writer error handler
                        }
                         
                    for (i = 0; i < MAXKEYS; i++) 
                    {
                    //
                    // lstrcmp requires that both parameters are
                    // null-terminated.
                    //
                        if(lstrcmp(vkeys[i].pKeyString, szKeyStroke) 
                            == 0) 
                        { 
                            hr = StringCchCopy(szKeyStroke, 16, vkeys[i].pKeyName);
                            if (FAILED(hr))
                            {
                            // TODO: write error handler
                            }
                            break; 
                        } 
                    } 
 
                    // Concatenate the keystroke text to the 
                    // "Ctl+","Alt+", or "Shft+" string. 
 
                        hr = StringCchCat(szAccelText, 32, szKeyStroke);
                        if (FAILED(hr))
                        {
                        // TODO: write error handler
                        }
 
                    // Determine the position in the menu of the 
                    // selected menu item. Menu items in the 
                    // "Character" menu have positions 0,2,3, and 4.
                    // Note: the lstrcmp parameters must be
                    // null-terminated. 
 
                    if (lstrcmp(szItem, "Regular") == 0) 
                        uItemPos = 0; 
                    else if (lstrcmp(szItem, "Bold") == 0) 
                        uItemPos = 2; 
                    else if (lstrcmp(szItem, "Italic") == 0) 
                        uItemPos = 3; 
                    else if (lstrcmp(szItem, "Underline") == 0) 
                        uItemPos = 4; 
 
                    // Get the string that corresponds to the 
                    // selected item. 
 
                    GetMenuString(hmenu, uItemPos, szItem, 
                        sizeof(szItem)/sizeof(TCHAR), MF_BYPOSITION); 
 
                    // Append the new accelerator text to the 
                    // menu-item text. 
 
                    for (pch = szItem; *pch != '\t'; pch++); 
                        ++pch; 
 
                    for (pch2 = szAccelText; *pch2 != '\0'; pch2++) 
                        *pch++ = *pch2; 
                    *pch = '\0'; 
 
                    // Modify the menu item to reflect the new 
                    // accelerator text. 
 
                    idItem = GetMenuItemID(hmenu, uItemPos); 
                    ModifyMenu(hmenu, idItem, MF_BYCOMMAND | 
                        MF_STRING, idItem, szItem); 
 
                    // Reset the selection flags. 
 
                    fItemSelected = FALSE; 
                    fKeySelected = FALSE; 
 
                    // Save the current accelerator table. 
 
                    haccelOld = haccel; 
 
                    // Count the number of entries in the current 
                    // table, allocate a buffer for the table, and 
                    // then copy the table into the buffer. 
 
                    cAccelerators = CopyAcceleratorTable( 
                        haccelOld, NULL, 0); 
                    lpaccelNew = (LPACCEL) LocalAlloc(LPTR, 
                        cAccelerators * sizeof(ACCEL)); 
 
                    if (lpaccelNew != NULL) 
                    {
                        CopyAcceleratorTable(haccel, lpaccelNew, 
                            cAccelerators); 
                    }
 
                    // Find the accelerator that the user modified 
                    // and change its flags and virtual-key code 
                    // as appropriate. 
 
                    for (i = 0; i < (UINT) cAccelerators; i++) 
                    { 
                           if (lpaccelNew[i].cmd == (WORD) idItem)
                        {
                            lpaccelNew[i].fVirt = fAccelFlags; 
                            lpaccelNew[i].key = wVKCode; 
                        }
                    } 
 
                    // Create the new accelerator table, and 
                    // destroy the old one. 
 
                    DestroyAcceleratorTable(haccelOld); 
                    haccel = CreateAcceleratorTable(lpaccelNew, 
                        cAccelerators); 
 
                    // Destroy the dialog box. 
 
                    EndDialog(hwndDlg, TRUE); 
                    return 0; 
 
                case IDCANCEL: 
                    EndDialog(hwndDlg, TRUE); 
                    return TRUE; 
 
                default: 
                    break; 
            } 
        default: 
            break; 
    } 
    return FALSE; 
}