キーボード アクセラレータの使用

このセクションでは、キーボード アクセラレータに関連付けられているタスクについて説明します。

アクセラレータ テーブル リソースの使用

アプリケーションにアクセラレータのサポートを追加する最も一般的な方法は、アプリケーションの実行可能ファイルにアクセラレータ テーブル リソースを含め、実行時にリソースを読み込む方法です。

このセクションでは、次のトピックについて説明します。

アクセラレータ テーブル リソースの作成

アクセラレータ テーブル リソースを作成するには、アプリケーションのリソース定義ファイルで 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_COMMAND または WM_SYSCOMMAND メッセージを受け取ります。 wParam パラメーターの下位ワードには、アクセラレータの識別子が含まれています。 ウィンドウ プロシージャは、識別子を調べて 、WM_COMMAND メッセージのソースを特定し、それに応じてメッセージを処理します。

通常、アクセラレータがアプリケーションのメニュー項目に対応する場合、アクセラレータとメニュー項目には同じ識別子が割り当てられます。 WM_COMMAND メッセージがアクセラレータまたはメニュー項目によって生成されたかどうかを知る必要がある場合は、wParam パラメーターの上位ワードを調べることができます。 アクセラレータがメッセージを生成した場合、高次ワードは 1 です。メニュー項目がメッセージを生成した場合、上位ワードは 0 になります。

アクセラレータ テーブル リソースの破棄

システムは 、LoadAccelerators 関数によって読み込まれたアクセラレータ テーブル リソースを自動的に破棄し、アプリケーションが閉じるとメモリからリソースを削除します。

フォント属性のアクセラレータの作成

このセクションの例では、次のタスクを実行する方法を示します。

  • アクセラレータ テーブル リソースを作成します。
  • 実行時にアクセラレータ テーブルを読み込みます。
  • メッセージ ループ内のアクセラレータを変換します。
  • アクセラレータによって生成された メッセージWM_COMMAND 処理します。

これらのタスクは、 ユーザー が現在のフォントの属性を選択できるようにする文字メニューと対応するアクセラレータを含むアプリケーションのコンテキストで示されています。

リソース定義ファイルの次の部分では、[ 文字 ] メニューと関連付けられているアクセラレータ テーブルを定義します。 メニュー項目にはアクセラレータのキーストロークが表示され、各アクセラレータには関連付けられているメニュー項目と同じ識別子があることに注意してください。

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

アプリケーションのソース ファイルの次のセクションでは、アクセラレータを実装する方法を示します。

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

実行時に作成されたアクセラレータ テーブルの使用

このトピックでは、実行時に作成されたアクセラレータ テーブルを使用する方法について説明します。

Run-Time アクセラレータ テーブルの作成

実行時にアクセラレータ テーブルを作成する最初の手順は、 ACCEL 構造体の配列を入力することです。 配列内の各構造体は、テーブルにアクセラレータを定義します。 アクセラレータの定義には、そのフラグ、キー、および識別子が含まれます。 ACCEL 構造体の形式は次のとおりです。

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

アクセラレータのキーストロークを定義するには、ACCEL 構造体のキー メンバーに ASCII 文字コードまたは仮想キー コードを指定します。 仮想キー コードを指定する場合は、まず fVirt メンバーに FVIRTKEY フラグを含める必要があります。それ以外の場合、システムはコードを ASCII 文字コードとして解釈します。 FCONTROLFALT、または FSHIFT フラグ、または 3 つすべてを含め、Ctrl キー、Alt キー、または Shift キーをキーストロークと組み合わせることができます。

アクセラレータ テーブルを作成するには、 ACCEL 構造体の配列へのポインターを CreateAcceleratorTable 関数に渡します。 CreateAcceleratorTable は アクセラレータ テーブルを作成し、ハンドルをテーブルに返します。

処理アクセラレータ

実行時に作成されたアクセラレータ テーブルによって提供されるアクセラレータを読み込んで呼び出すプロセスは、アクセラレータ テーブル リソースによって提供されるアクセラレータの処理と同じです。 詳細については、「WM_COMMAND メッセージの処理によるアクセラレータ テーブル リソースの読み込み」を参照してください。

Run-Time アクセラレータ テーブルの破棄

システムは、実行時に作成されたアクセラレータ テーブルを自動的に破棄し、アプリケーションの終了後にメモリからリソースを削除します。 テーブルのハンドルを DestroyAcceleratorTable 関数に渡すことで、アクセラレータ テーブルを破棄し、メモリから削除できます。

ユーザー編集可能アクセラレータの作成

この例では、ユーザーがメニュー項目に関連付けられているアクセラレータを変更できるようにするダイアログ ボックスを作成する方法を示します。 ダイアログ ボックスは、メニュー項目を含むコンボ ボックス、キーの名前を含むコンボ ボックス、および Ctrl キー、Alt キー、および Shift キーを選択するためのチェック ボックスで構成されます。 次の図は、 ダイアログ ボックスを示しています。

コンボ ボックスとチェック ボックスを含むダイアログ ボックス

次の例は、リソース定義ファイルでダイアログ ボックスがどのように定義されているかを示しています。

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

アプリケーションのメニュー バーには、項目にアクセラレータが関連付けられている Character サブメニューが含まれています。

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

メニュー テンプレートのメニュー項目の値は、アプリケーションのヘッダー ファイルで次のように定義されている定数です。

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

このダイアログ ボックスでは、アプリケーション定義の VKEY 構造体の配列が使用され、それぞれにキーストローク テキスト文字列とアクセラレータ テキスト文字列が含まれています。 ダイアログ ボックスが作成されると、配列が解析され、各キーストローク テキスト文字列が [キーストロークの選択 ] コンボ ボックスに追加されます。 ユーザーが [OK ] ボタンをクリックすると、選択したキーストローク テキスト文字列がダイアログ ボックスで検索され、対応するアクセラレータ テキスト文字列が取得されます。 ダイアログ ボックスは、ユーザーが選択したメニュー項目のテキストにアクセラレータ テキスト文字列を追加します。 次の例は、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; 
}