Log in

View Full Version : Accelerator Keys


Tech19
March 12th, 2002, 22:18
How can i attach an accelerator key to a menu function. For example i want to make it so that when i press Ctrl+A in notepad it Selects all. I have modified the Edit menu to say the Select All can be accessed by pressing Ctrl+A, now all i need to do is make it do that Any help appreciated...

DakienDX
March 13th, 2002, 06:50
Hello Tech19 !

Open Notepad in a Resource Editor, choose Accelerators\1\YourLanguage and Accelerators\2\YourLanguage and insert "^A", 7" (On my Notepad it's 7, maybe your's is different, check it in the menu where you've already edited to insert \tCtrl+A)

Then save the file and enjoy.

Kayaker
March 13th, 2002, 07:16
Hi

Unfortunately with notepad most of your work is already done. You've redefined the Select All menu string to display an accelerator string, now you just need to define it under the Accelerators resource using your favorite resource editor. Just mimic the syntax for an already existing accelerator since they're all defined slightly differently if you use Reshack, Exescope, Borland Resource Editor, etc.

(Actually it's exactly as DakienDX said, heh)

This should be all you need to do and the code should now accept your Ctrl-A. The question is, *why* is this all you need to do in notepad? When you select a menu item the WM_COMMAND message is sent to the menu window. If you've added the accelerator as above and set a bmsg breakpoint on the hWnd of the menu bar and select Ctrl-A you'll get this info from Softice:

:bmsg 2ec wm_command
Break due to BMSG 02EC WM_COMMAND (ET=1.35 seconds)
hWnd=02EC wParam=0007 lParam=00010000 msg=0111 WM_COMMAND

The high-order word of wParam is the resource ID of Select All (007) and the low-order word indicates the message came from an accelerator (0001). This is clarified in the Win32 Programmers Reference:

------------------------------------------------------------
WM_COMMAND
wNotifyCode = HIWORD(wParam); // notification code
wID = LOWORD(wParam); // item, control, or accelerator identifier
hwndCtl = (HWND) lParam; // handle of control

wNotifyCode
Value of the high-order word of wParam. Specifies the notification code if the message is from a control. If the message is from an accelerator, this parameter is 1. If the message is from a menu, this parameter is 0.

wID
Value of the low-order word of wParam. Specifies the identifier of the menu item, control, or accelerator.

hwndCtl
Value of lParam. Identifies the control sending the message if the message is from a control. Otherwise, this parameter is NULL.
----------------------------------------------------------------

Now you need to find where in code the WM_COMMAND message is processed. If you look at the code line you broke at on the BMSG breakpoint you'll see:

141F:07DE 6668741C4000 PUSH 00401C74
141F:07E4 666800000300 PUSH 00030000
141F:07EA EA16764F01 JMP 014F:7616

That very first address (401C74) is actually the start of the programs main message processing loop. So if you set a bpx breakpoint on it, specifying your regular CS value, i.e.
bpx 167:401C74
you'll be able to continue the tracing with the Select All message parameters as they are being processed.

Find where the WM_COMMAND message (111) is compared and you'll continue on to where the Resource ID's begin to be compared. You can actually find this easily in Wdasm by searching for the other menu commands such as Save and Copy.

You should find this snippet of code:

:0040125A 0FB7750C movzx esi, word ptr [ebp+0C]
:0040125E 83FE20 cmp esi, 00000020

This first line passes the stack parameter [ebp+0C] to esi. This is your Resource ID (0007 for Select All, 0301 for Copy, etc.). It then goes through a series of compares to find the proper routine for the proper menu item selected.

Now, the point of this whole explanation isn't to confuse the issue, but to point out something you'd need to know if you want to add accelerator keys to *other* applications that may handle this a bit differently, or if you want to patch in a new menu item/accelerator to handle some of your own code.

Notice the stack parameter [ebp+0C] is handled as a WORD PTR. With this syntax this effectively masks out the low-order word of wParam, which would indicate if it was from an accelerator (0001) or not (0000). If you were to check [ebp+0C] as a DWORD you'd see it was actually 00010007 if the accelerator was used. OK, so what you say?

Well, for notepad you don't need to do anything else, the code is taking care of all situations whether an accelerator is used or not. Look at how this is handled in a different app, Regmon:

------------------------------------------------
:00405BBD 25FFFF0000 and eax, 0000FFFF
; this line masks out the LOWORD(wParam)=0001 Accelerator key modifier
.
.
* Possible Ref to Menu: LISTMENU, Item: "Save Ctrl+S"
|
:00405BE4 3D479C0000 cmp eax, 00009C47 ;MenuItemID of "Save"
:00405BE9 7F69 jg 00405C54
--------------------------------------------------

Now if you were to patch in a new menu item somewhere, it would *have* to be after the accelerator key is handled, by essentially masking it out and ignoring it for the rest of the code, or you'd have to handle it yourself.

All I'm really trying to get across here is showing how to identify what the messaging loop is doing and how it handles accelerator keys, and point out that the *placement* of an inline patch to handle a new routine from a menu item is critical. If you ignored the accelerator key (if you used one), then you might find your code didn't work when you used the key and might wonder why.

Enough ramblings, hope this helped or something

Cheers,
Kayaker