Adding Menu Accelerator Keys There is one feature of Windows that is commonly used in conjunction with a menu. This feature is the accelerator key. Accelerator keys are special keystrokes that you define which, when pressed, automatically select a menu option even though the menu in which that option resides is not displayed. Put differently, you can select an item directly by pressing an accelerator key, bypassing the menu entirely. The term accelerator key is an accurate description because pressing one is generally a faster way to select a menu item than first activating its menu and then selecting the item. To define accelerator keys relative to a menu, you must add an accelerator key table to your resource file. An accelerator table has this general form: TableNamee ACCELERATORS \[acccl-options] { Keyl, MenuID1 \[, type] \[options] Key2, MenuID2 \[, type] \[options] Key3, MenuID3 \[, type] \[options] . . . KeyN, MenuIDN \[, type] \[options] } Here, Tublename is the name of the accelerator table. An ACCELERATORS statement can have the same options as those described for MENU. If needed, they are specified by accel-options. However, most applications simply use the default settings. Inside the accelerator table, Key is the keystroke that selects the item and MenuID is the ID value associated with the desired item. The type specifies whether the key is a standard key (the default) or a virtual key. The options may be one of the following macros: NOINVERT, ALT, SHIFT, and CONTROL. NOINVERT prevents the selected menu item from being highlighted when its accelerator key is pressed. ALT specifies an alt key. SHIFT specifies a shift key. CONTROL specifies a ctrl key. The value of Key will be either a quoted character, an ASCII integer value corresponding to a key, or a virtual key code. If a quoted character is used, then it is assumed to be an ASCII character. If it is an integer value, then you must tell the resource compiler explicitly that this is an ASCII character by specifying type as ASCII. If it is a virtual key, then type must be VIRTKEY. If the key is an uppercase quoted character then its corresponding menu item will be selected if it is pressed while holding down the shift key. If it is a lowercase character, then its menu item will be selected if the key is pressed by itself. If the key is specified as a lowercase character and ALT is specified as an option, then pressing alt and the character will select the item. (If the key is uppercase and ALT is specified, then you must press shiff and alt to select the item.) Finally, if you want the user to press ctrl and the character to select an item, precede the key with a ^. As explained in lecture 4, a virtual key is a system-independent code for a variety of keys. To use a virtual key as an accelerator, simply specify its macro for the key and specify VIRTKEY for its type. You may also specify ALT, SHIFT, or CONTROL to achieve the desired key combination. Here are some examples: "A", IDM_x ; select by pressing Shift-A "a", IDM_x " ; select by pressing a "^a", IDM_x " ; select by pressing Ctrl-a "a", IDM_x, ALT ; select by pressing Alt-a VK_F2, IDM_x ; select by pressing F2 VK_F2, IDM_x, SHIFT ; select by pressing Shift-F2 Here is the MENU.RC resource file that also contains accelerator key definitions for MyMenu. ; Sample menu resource file and accelerators. # include \<windows.h> # include "menu.h" MyMenu MENU {POPUP "&File" {MENUITEM "&Open\\tF2", IDM_OPEN MENUITEM "&Close\\tF3", IDM_CLOSE MENUITEM "&Exit \\t Ctrl-X", IDM_EXIT} POPUP "^Options" { MENUITEM "& Colors\\t Ctrl-C", IDM_COLORS POPUP"&Priority"{ MENUITEM "&Low\\tF4", IDM_LOW MENUITEM"&High\\tF5", IDM_HIGH} MENUITEM "&Fonts\\t Ctrl-F", IDM_FONT MENUITEM"&Resolution\\tCtrl-R",IDM_RESOLUTION } MENUITEM "&Help", IDM_HELP} //Define menu accelerators MyMenu ACCELERATORS { VK_F2, IDM_OPEN, VIRTKEY VK_F3, IDM_CLOSE, VIRTKEY "^X", IDM_EXIT "^C", IDM_COLORS VK_F4, IDM_LOW, VIRTKEY VK_F5, IDM_HIGH, VIRTKEY "^F", IDM_FONT "^R", IDM_RESOLUTION VK_F1, IDM_HELP, VIRTKEY } Notice that the menu definition has been enhanced to display which accelerator key selects which option. Each item is separated from its accelerator key using a tab. The header file WINDOWS.H is included because it defines the virtual key macros. Loading the Accelerator Table Even though the accelerators are contained in the same resource file as the menu, hey must be loaded separately using another API function called LoadAccelerators( ), whose prototype is shown here: HACCEL LoadAccelerators (HlNSTANCE Thislnst, LPCSTR Name); where Thislnst is the instance handle of the application and Name is the name of the accelerator table. The function returns a handle to the accelerator table or NULL if the table cannot be loaded. You must call LoadAccelerators( ) soon after the window is created. For example, this shows how to load the MyMenu accelerator table: HACCEL hAccel; hAccel = LoadAccelerators(hThisInst, "MyMenu"); The value of hAccel will be used later to help process accelerator keys. Translating Accelerator Keys Although the LoadAccelerators() function loads the accelerator table, your program still cannot process accelerator keys until you add another API function to the message loop. This function is called TranslateAcceIerator( ) and its prototype is shown here: int TransIateAccclerator(HWND hwnd, HACCEL hAccel, LPMSG lpMess); Here, hwnd is the handle of the window for which accelerator keys will be translated. hAcccl is the handle to the accelerator table that will be used. This is the handle returned by LoadAccelerators( ). Finally, lpMess is a pointer to the message. The TrauslateAcceIerator( ) function returns true if an accelerator key was pressed and false otherwise. TranslateAcccIerator( ) translates an accelerator keystroke into its corresponding WM_COMMAND message and sends that message to the window. In this message, the value of LOWORD(wParam) will contain the ID associated with the accelerator key. Thus, to your program, the WM_COMMAND message will appear to have been generated by a menu selection. Since TranslateAccelerator( ) sends a WM_COMMAND message whenever an accelerator key is pressed, your program must not execute TranslateMessage( ) or DispatchMessage( ) when such a translation takes place. When using TranslateAccelcrator( ), your message loop should look like this: while(GetMessage(&msg, NULL, 0, 0)) {if(!TranslateAccelerator(hwnd, hAccel, &msg)) {TranslateMessage(&msg); /*allow use of keyboard */ DispatchMessage(&msg); /*return control to Windows*/}} Trying Accelerator Keys To try using accelerators, substitute the following version of WinMain( ) into the preceding application and add the accelerator table to your resource file. /* Process accelerator keys. */ #include \<windows.h> #include \<string.h> #include \<stdio.h> #include "menu.h" LRESULT CALLBACK WindowFunc(HWND, UINT, WPARAM, LPARAM); char szWinName\[ ] = "MyWin"; /* name of window class */ int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR IpszArgs, int nWinMode) {HWND hwnd; MSG msg; WNDCLASSEX wc1; HACCEL hAccel; wc1.cbSize = sizeof(WNDCLASSEX); wc1 .hInstance = hThisInst; wc1 .lpszClassName = szWinName; wc1. lpfnWndProc = WindowFunc; wc1. style =0; wc1.hIcon = LoadIcon(NULL, IDI_APPLICATION) ; wc1.hlconSm = LoadIcon (NULL, IDI_WINLOGO) ; wc1.hCursor = LoadCursor (NULL, IDC_ARROW) ; wc1. lpszMenuName = "MyMenu"; wc1 .cbClsExtra =0; wc1 .cbWndExtra = 0; wc1.hbrBackground = GetStockObject (WHITE_BRUSH) ; if ( !RegisterClassEx (&wc1) ) return 0 ; hwnd = CreateWindow WS_OVERLAPPEDWINDOW, (szWinName,"Adding CW_USEDEFAULT, Accelerator Keys", CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,HWND_DESKTOP,NULL, hThisInst, NULL ); /* load the keyboard accelerators */ hAccel = LoadAccelerators (hThisInst, "MyMenu") ; /*Display the window.*/ ShowWindow (hwnd, nWinMode) ; UpdateWindow(hwnd) ; while(GetMessage(&msg, NULL, 0, 0)){if ( ! TranslateAccelerator (hwnd,' hAccel,. &msg) ) {TranslateMessage(&msg); DispatchMessage(&msg)}} return msg. wParam; } In Depth: A Closer Look at WM_COMMAND As you know, each time you make a menu selection or press an accelerator key, a WM_COMMAND message is sent and the value in LOWORD(wParam) contains the ID of the menu item selected or the accelerator key pressed. However, using only the value in LOWORD(wParam) it is not possible to determine which event occurred. In most situations, it doesn't matter whether the user actually made a menu selection or just pressed an accelerator key. But in those situations in which it does, you can find out because Windows provides this information in the high-order word of wParam. If the value in HIWORD(wParam) is 0, then the user has made a menu selection. If this value is 1, then the user pressed an accelerator key. For example, try substituting the following fragment into the menu program. It reports whether the Open option was selected using the menu or by pressing an accelerator key. case IDM_OPEN: if(HIWORD(wParam)) MessageBox(hwnd, "Open File via Accelerator", "Open", MB_OK); Else MessageBox(hwnd, "Open File via Menu Selection", "Open", MB_OK); break; The value of IParam for WM_COMMAND messages generated by menu selections or accelerator keys is unused and always contains NULL. As you will see in the next lecture, a WM_COMMAND is also generated n when the user interacts with various types of controls. In this case, the meanings of lParam and wParam are somewhat different. For example, the value of lParam will contain the handle of the control. Non-Menu Accelerator Keys Although keyboard accelerators are most commonly used to provide a fast means of selecting menu items, they are not limited to this role. For example, you can define an accelerator key for which there is no corresponding menu item. You might use such a key to activate a keyboard macro or to initiate some frequently used option. To define a non-menu accelerator key, simply add it to the accelerator table, assigning it a unique ID value. As an example, let's add a non-menu accelerator key to the menu program,| The key will be ctrl-t and each time it is pressed, the current time and date are displayed in a message box. The standard ANSI C time and date functions are used to obtain the current time and date. To begin, change the key table so that it looks like this: MyMenu ACCELERATORS {VK_F2, IDM_OPEN, VIRTKEY VK_F3, IDM_CLOSE, VIRTKEY "^X", IDM_EXIT "^C", IDM_COLORS VK_F4, IDM_LOW, VIRTKEY VK_F5, IDM_HIGH, VIRTKEY "^R", IDM_RESOLUTION "^F", IDM_FONT VK_F1, IDM_KELP, VIRTKEY "^T", IDM_TIME} Next, add this line to MENU.H: #define IDM_TIME 500 Finally, substitute this version of WindowFunc( ) into the menu program You will also need to include the TIME.H header file. LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {int response; struct tm *tod; time_t t; char str \[80]; switch(message) {case WM_COMMAND: switch(LOWORD(wParam)) {case IDM_OPEN: MessageBox(hwnd, "Open File", "Open", MB_OK);break; case IDM_CLOSE: MessageBox(hwnd, "Close File", "Close", MB_OK);break; case IDM_EXIT:response=MessageBox(hwnd,"Quit the Program?","Exit", MB_YESNO); if(response == IDYES) PostQuitMessage(0); break; case IDM_COLORS: MessageBox(hwnd, "Set Colors", "Colors", MB_OK);break; case IDM_LOW: MessageBox(hwnd, "Low", "Priority", MB_OK);break; case IDM_HIGH: MessageBox(hwnd, "High", "Priority", MB_OK);break; case IDM_RESOLUTION: MessageBox(hwnd,"Resolution Options","Resolution", MB_OK);break; case IDMJFONT: MessageBox(hwnd, "Font Options", "Fonts", MB_OK); break; case IDM_TIME: /* show time */ t = time(NULL); tod = localtime(&t); strcpyfstr, asctime(tod)); str(strlen(str)-1] = '\\0'; /* remove /r/n */ MessageBox(hwnd, str, "Time and Date", MB_OK); break; case IDM_HELP: MessageBox(hwnd,"No Help","Help",MB_OK); break; }break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, message, wParam, lParam);} return 0; } When you run this program, each time you press ctrl-t, you will see a message box similar to the following: