키보드 액셀러레이터 사용

이 섹션에서는 키보드 가속기와 연결된 작업에 대해 설명합니다.

Accelerator 테이블 리소스 사용

애플리케이션에 가속기 지원을 추가하는 가장 일반적인 방법은 애플리케이션의 실행 파일에 accelerator-table 리소스를 포함하고 런타임에 리소스를 로드하는 것입니다.

이 섹션에서는 다음 topics 다룹니다.

Accelerator 테이블 리소스 만들기

애플리케이션의 리소스 정의 파일에서 ACCELERATORS 문을 사용하여 accelerator-table 리소스를 만듭니다. 다른 리소스와 달리 가속기 테이블에 이름 또는 리소스 식별자를 할당해야 합니다. 시스템은 이 식별자를 사용하여 런타임에 리소스를 로드합니다.

정의하는 각 액셀러레이터에는 액셀러레이터 테이블에 별도의 항목이 필요합니다. 각 항목에서 가속기와 가속기 식별자를 생성하는 키 입력(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 

Accelerator 테이블 리소스 로드

애플리케이션은 LoadAccelerators 함수를 호출하고 실행 파일에 리소스 및 리소스의 이름 또는 식별자가 포함된 애플리케이션에 대한 instance 핸들을 지정하여 accelerator-table 리소스를 로드합니다. LoadAccelerators는 지정된 가속기 테이블을 메모리에 로드하고 핸들을 가속기 테이블에 반환합니다.

애플리케이션은 언제든지 accelerator-table 리소스를 로드할 수 있습니다. 일반적으로 단일 스레드 애플리케이션은 기본 메시지 루프를 입력하기 전에 가속기 테이블을 로드합니다. 여러 스레드를 사용하는 애플리케이션은 일반적으로 스레드에 대한 메시지 루프를 입력하기 전에 스레드에 대한 accelerator-table 리소스를 로드합니다. 애플리케이션 또는 스레드는 애플리케이션의 특정 창과 연결된 여러 가속기 테이블을 사용할 수도 있습니다. 이러한 애플리케이션은 사용자가 창을 활성화할 때마다 창에 대한 가속기 테이블을 로드합니다. 스레드에 대한 자세한 내용은 프로세스 및 스레드를 참조하세요.

Translate Accelerator 함수 호출

가속기를 처리하려면 애플리케이션의 메시지 루프(또는 스레드)에 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입니다.

Accelerator 테이블 리소스 삭제

시스템은 LoadAccelerators 함수에 의해 로드된 액셀러레이터 테이블 리소스를 자동으로 제거하여 애플리케이션이 닫히면 메모리에서 리소스를 제거합니다.

글꼴 특성에 대한 가속기 만들기

이 섹션의 예제에서는 다음 작업을 수행하는 방법을 보여 줍니다.

  • accelerator-table 리소스를 만듭니다.
  • 런타임에 가속기 테이블을 로드합니다.
  • 메시지 루프에서 가속기를 변환합니다.
  • 가속기 에서 생성된 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 문자 코드로 해석합니다. FCONTROL, FALT 또는 FSHIFT 플래그 또는 세 가지 모두를 포함하여 CTRL, ALT 또는 SHIFT 키를 키 입력과 결합할 수 있습니다.

가속기 테이블을 만들려면 ACCEL 구조의 배열에 대한 포인터를 CreateAcceleratorTable 함수에 전달합니다. CreateAcceleratorTable 은 가속기 테이블을 만들고 테이블에 대한 핸들을 반환합니다.

처리 가속기

런타임에 만든 가속기 테이블에서 제공하는 가속기를 로드하고 호출하는 프로세스는 accelerator-table 리소스에서 제공하는 가속기를 처리하는 프로세스와 동일합니다. 자세한 내용은 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; 
}