News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Anyone here know how to make keyboard accelerators work?

Started by NoCforMe, June 16, 2022, 11:50:06 AM

Previous topic - Next topic

NoCforMe

Need help again. This time with keyboard accelerators.

I want to be able to use Ctrl-S to do a save in a program of mine. Seems simple enough. But no, because you can't just look at a key (say when you get a WM_CHAR or WM_KEYDOWN message) and see if the Ctrl key is down. (You can do this with the Alt key, though.)

Anyhoo, I slogged through the MSDN docs and came up with the following. Keep in mind that this is being added to a program that was working fine before I started messing with it.

I added this code just before my message loop:

; Create Ctrl-S keyboard accelerator:
MOV accel.fVirt, FCONTROL ;or FVIRTKEY
MOV accel.key, 53H ;'S'
MOV accel.cmd, $menuSaveAll
INVOKE CreateAcceleratorTable, ADDR accel, 1
MOV AccelHandle, EAX

(AccelHandle is a global variable. I tried both with the FVIRTKEY flag and without; again a little confused, as the virtual-key value for the 'S' key is the same as its ASCII code, 53H.)

Then I modified my message loop to look like this:


;============= Message loop ===================
mssglp: INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit99

INVOKE TranslateAccelerator, MainWinHandle, AccelHandle, ADDR msg
OR EAX, EAX
JNZ mssglp

INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP mssglp


Does not work. Does nothing when Ctrl-S is pressed.

Here's where I'm confused. I couldn't find any working examples of this online, only samples from other people who were having the same problem as me and who never got a good answer as to why.

I'm confused by the logic of using TranslateAccelerator(). Does it work this way: if it sees an accelerator, it puts it in the message queue, so you should go back to GetMessage()? or should you just continue on to TranslateMessage()? I've tried it both ways, and neither one works.

Reading the docs for TranslateAccelerator(), it says that it returns nonzero if it succeeds and zero if it fails. So if it's processing an accelerator message, it should return nonzero, right?

Anyhow, I'd really appreciate it if someone who actually knows how this works would reply here. I really don't need any speculation on this from those who don't know.

Thanks in advance.
Assembly language programming should be fun. That's why I do it.

NoCforMe

OK, so the way I understand it, if TranslateAccelerator() gets an accelerator message, it handles it itself--sends it to the correct window (the one who handle you passed to it). So if it succeeds, you should skip the usual TranslateMessage() and DispatchMessage() processing and just go back to get the next message in the queue. Is this correct?

In any case, it still doesn't work ...


;============= Message loop ===================
mssglp: INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit99

INVOKE TranslateAccelerator, MainWinHandle, AccelHandle, ADDR msg
OR EAX, EAX ;If this was an accelerator message, it's been handled,
JNZ mssglp ;  so go back and get the next one.

INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP mssglp


Later: Confirmed by this page
Assembly language programming should be fun. That's why I do it.

NoCforMe

OK, I got it working. The problem wasn't with the accelerator code itself; it was a much more complicated problem, which I actually solved all my myself. May be instructive to describe it.

The problem was one of keyboard focus. My program starts with a main window which spawns a child window that's used for editing a dialog template. When a dialog is being edited, that's the window that has the keyboard focus, not the main window, which is why I wasn't getting any accelerator action.

So I had to get a little tricky. I changed the TranslateAccelerator() call to use the dialog-editing window handle. Problem: this handle didn't exist until I created or loaded a dialog to edit. Since I didn't want bad things to happen by calling this function with a bogus handle, I had to protect it:


;============= Message loop ===================
mssglp: INVOKE GetMessage, ADDR msg, NULL, 0, 0
TEST EAX, EAX ;EAX = 0 = exit
JZ exit99

; Check for keyboard accelerators ONLY if dialog-editing window is up
; (because it'll have the keyboard focus):
CMP DlgEditWinHandle, NULL
JE @F
INVOKE TranslateAccelerator, DlgEditWinHandle, AccelHandle, ADDR msg
TEST EAX, EAX ;If this was an accelerator message, it's been handled,
JNZ mssglp ;  so go back and get the next one.

@@: INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP mssglp

exit99:


(I make sure to NULL-out this handle when the editing window closes)

Now the problem was that the accelerator command was being sent to the wrong window. All the menu-handling code (load, save, etc.) is in the main window. So I did a little song and dance: I looked for the WM_COMMAND message in my editing window instead. When I got it (checking for this one menu ID), I turned around and sent my own WM_COMMAND message to the right window (the main one). A little klugey, but it works. (Hey, it's called "inter-process communication"!)


(in dialog-editing proc)

MOV EAX, uMsg
CMP EAX, WM_CREATE
JE do_create
.....

; This is solely to catch the $menuSaveAll command:
CMP EAX, WM_COMMAND
JE do_command

; See if the WM_COMMAND msg. is for "Save all":
do_command:
CMP WORD PTR wParam, $menuSaveAll
JNE dodefault ;Nope.
INVOKE SendMessage, MainWinHandle, WM_COMMAND, $menuSaveAll, NULL
JMP dodefault
Assembly language programming should be fun. That's why I do it.

NoCforMe

BTW, worth pointing out that the reason I decided to try keyboard accelerators is that I had first tried doing this by setting up a "hot key", using RegisterHotKey(). That worked. But I had to abandon it because of a really weird side effect: if I was running my program with its hot key setup, and then used the same key (Ctrl-S) in another application (Notepad in this case), the keystroke activated my program! Dunno how or why that happened; apparently when you register a hot key it is visible outside of your thread/process/whatever. I figured it was just too damn much trouble to try to figure it out. Keyboard accelerators are much simpler, and they don't cross program boundaries!
Assembly language programming should be fun. That's why I do it.

Greenhorn

Put this in your message loop:


      invoke IsDialogMessage, DlgEditWinHandle, addr msg
      .if (!eax)
         invoke TranslateAccelerator, MainWinHandle, AccelHandle, addr msg
         .if (!eax)
            invoke TranslateMessage, addr msg
            invoke DispatchMessage,  addr msg
         .endif
      .endif



Kind Regards
Greenhorn
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

Assembly language programming should be fun. That's why I do it.

Greenhorn

Quote from: NoCforMe on June 18, 2022, 08:21:50 AM
Already did that. Didn't you read my code above?

I cannot find a call to IsDialogMessage in your message loop.

Maybe I got twisted that your child window isn't a dialog box or a child of a diaog box.

However, in my application the accelerators which don't belong to a control/child window/dialog box (with keyboard focus) are sent to my main window. For example, Ctrl+S, Ctrl+O, etc. works as expected.

So, normally your code from Reply #1 schould work fine without forwarding the message from your dialog-edit proc.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

Sorry, I missed that. No, I don't need that, as this isn't in a dialog. Probably my bad explanation. The program this is in creates a dialog template, but the window that the user interacts with isn't a dialog, just a regular window that gets populated with controls. So the code I put up works fine for me. If it were in an actual dialog I'd need to add IsDialogMessage() as you suggested.

Although I guess you can use this function with a non-dialog window. Maybe I'll try it later, see if I can avoid re-routing messages ...
Assembly language programming should be fun. That's why I do it.

Greenhorn

Quote from: NoCforMe on June 18, 2022, 10:22:03 AM
Sorry, I missed that. No, I don't need that, as this isn't in a dialog. Probably my bad explanation.
No, it seems more about my bad eyes in combination with a bad beer.  :biggrin:

Quote
The program this is in creates a dialog template, but the window that the user interacts with isn't a dialog, just a regular window that gets populated with controls. So the code I put up works fine for me. If it were in an actual dialog I'd need to add IsDialogMessage() as you suggested.

Although I guess you can use this function with a non-dialog window. Maybe I'll try it later, see if I can avoid re-routing messages ...
If your application doesn't use a dialog box you don't need a call to it.

And yes, normally it should work without forwarding the message.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

Greenhorn

Quote from: NoCforMe on June 18, 2022, 10:22:03 AM
Although I guess you can use this function with a non-dialog window.
Yes, for example if you want to implement "tabbing" through your controls of a non-dialog window.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

hutch--

 :biggrin:

> in combination with a bad beer.

Was it some of that Aldi stuff you mentioned ?  :tongue:

Greenhorn

> Was it some of that Aldi stuff you mentioned ?  :tongue:

:biggrin:

No, my fridge was empty and after a few glasses of tap water I remembered that I had some beer anywhere.
It was a present box with two bottles of finest "German Coast" beer and a beer glass.
However, it was orginally for a former co-worker in SELU but then I forgot to send it.
It was stored under way to warm conditions, so it was not the pleasure I've expected.

The day after I drunk the second one: ice cold and was a real joy.  :thumbsup:
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

hutch--

 :biggrin:

Unless you are English, warm beer is not very ice. I keep my "Pure Blonde" in the fridge and they are pleasant when really cold. I remember the worst beer I even consumed, when I was still in high school, a mate turned up with this bottle of "Balimba Gold Top" from up in north Queensland and to put it graciously, it tasted like what you would expect rats piss to taste like. It may have been better cold but I doubt by much.

jj2007


NoCforMe

Assembly language programming should be fun. That's why I do it.