News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Dialog windows: what control has focus?

Started by disintx, January 11, 2011, 11:21:54 AM

Previous topic - Next topic

disintx

Hey all! It's been a long time since my last post, hope you're all well! :green

I'm trying to get more used to writing something other than console apps.
Right now, I'm playing with dialog box windows.

I have searched all over the place and this is driving me mad: how can I detect what control was clicked on?
I can get the WM_LBUTTONDOWN, and lParam can tell me the (X,Y) coordinates, but that seems like a lot of
work to see what was clicked on.

Just in case I'm not making sense, I'm wondering if there is a message I seem to be missing that can inform me
_what_ control has focus, or was clicked.

For example, the following code I wrote functions when the main dialog window was clicked,
but not when the edit control was clicked.

include masm32rt.inc
include \masm32\macros\ucmacros.asm

DlgProc proto hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.data
; WSTR for oonicodes
WSTR msgBoxText, "You clicked it. Are you happy?"
WSTR msgBoxTitle, "~"

.data?
hInst HINSTANCE ?
.const
IDD_DLG1                equ 1000
IDC_EDT1                equ 1001

.code
start:
invoke GetModuleHandle, NULL
mov hInst, eax ;get our handle
invoke DialogBoxParam, hInst, IDD_DLG1, NULL, addr DlgProc, NULL
invoke ExitProcess, eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_LBUTTONDOWN
;mov eax, lParam
;.if ax <... was detecting where they clicked and then deciding to do this (x, y)
invoke SetDlgItemTextW, hWnd, IDC_EDT1, addr msgBoxText
invoke MessageBoxW, 0, addr msgBoxText, addr msgBoxTitle, 0
;.endif
.elseif uMsg == WM_CLOSE
invoke EndDialog, hWnd, 0
.endif
xor eax, eax
ret
DlgProc endp
end start


Thanks for any information you can provide.

Tight_Coder_Ex

WM_ACTIVATE is the message you want to catch and it tells you how the window was addressed such as with the mouse or another part of the program setting focus.

japheth

Quote from: Tight_Coder_Ex on January 11, 2011, 01:21:14 PM
WM_ACTIVATE is the message you want to catch and it tells you how the window was addressed such as with the mouse or another part of the program setting focus.

WM_ACTIVATE won't help to track what child window has the focus. Usually the children tell their parent if they are about to get or loose the focus. For edit controls, there's the EN_SETFOCUS and EN_KILLFOCUS notifications, for "common controls" there is NM_SETFOCUS/NM_KILLFOCUS.

Tight_Coder_Ex

[quote author=japheth link=topic=15855.msg130960#msg130960 date=1294752929WM_ACTIVATE won't help to track what child window has the focus. Usually the children tell their parent if they are about to get or loose the focus. For edit controls, there's the EN_SETFOCUS and EN_KILLFOCUS notifications, for "common controls" there is NM_SETFOCUS/NM_KILLFOCUS.
Quote

Yes and for controls also BN_CLICKED can be caught through WM_COMMAND

donkey

I guess you could do it this way. I typed this directly into the reply and have not tested it but its probably close to right and will give you an idea what I'm aiming at. In your main dialog proc you would process WM_USER (child window has obtained focus) WM_USER+1 (child window has lost focus). The only advantage to this method is that there are only 2 messages to process in your main dialog procedure, all child windows capable of receiving the focus including edits, buttons and common controls will send the same message.

invoke EnumChildWindows,[hDlg], offset EnumChildProc, offset FocusTrackSubClass

EnumChildProc FRAME hwnd, lParam
    invoke SetWindowSubclass,[hwnd],[lParam],0,0
    mov eax,TRUE
    RET
ENDF

FocusTrackSubClass FRAME hwnd, uMsg, wParam, lParam, uIdSubclass, dwRefData

    cmp D[uMsg], WM_SETFOCUS
    jne >>.WM_KILLFOCUS
        invoke GetAncestor, [hwnd],GA_ROOT
        invoke PostMessage, eax, WM_USER, [hwnd],0
        jmp >>.DEFPROC

    .WM_KILLFOCUS
    cmp D[uMsg], WM_KILLFOCUS
    jne >>.DEFPROC
        invoke GetAncestor, [hwnd],GA_ROOT
        invoke PostMessage, eax, WM_USER+1, [hwnd],0
        jmp >>.DEFPROC

    .DEFPROC
    invoke DefSubclassProc, [hwnd], [uMsg], [wParam], [lParam]

    RET
ENDF

"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

disintx

Thanks for all the replies, and thanks Donkey for the code.

Here's what I've worked out, before I tried BN_CLICKED.

include masm32rt.inc
include x:\masm32\macros\ucmacros.asm

EnumChildren proto hWnd:HWND, lParam:LPARAM
TrackSubclass proto hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM, uIdSubclass:DWORD, dwRefData:DWORD
DlgProc proto hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.data
WSTR msgBoxText, "You clicked it. Are you happy?"
WSTR msgBoxTitle, "Okay?"
;WSTR msgBoxTmp, "Caught WM_SETFOCUS or WM_KILLFOCUS"
.data?
hInst HINSTANCE ?
.const
IDD_DLG1                equ 1000
IDC_EDT1                equ 1001

.code
start:
;invoke MessageBoxW, 0, addr msgBoxText, addr msgBoxTitle, 0
invoke GetModuleHandle, NULL
mov hInst, eax ;get our handle
invoke DialogBoxParam, hInst, IDD_DLG1, NULL, addr DlgProc, NULL
invoke EnumChildWindows, hInst, addr EnumChildren, addr TrackSubclass
invoke ExitProcess, eax

EnumChildren proc hWnd:HWND, lParam:LPARAM
invoke SetWindowSubclass, hWnd, lParam, 0, 0
xor eax, eax
ret
EnumChildren endp

TrackSubclass proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM, uIdSubclass:DWORD, dwRefData:DWORD
; not quite sure what type uIdSubclass takes but I am not using it so it's not important right now
.if uMsg == WM_SETFOCUS
; invoke MessageBoxW, 0, addr msgBoxTmp, addr msgBoxTmp, 0
invoke GetAncestor, hWnd, GA_PARENT
invoke PostMessage, eax, WM_USER, hWnd, 0
.elseif uMsg == WM_KILLFOCUS
invoke GetAncestor, hWnd, GA_PARENT
invoke PostMessage, eax, WM_USER+1, hWnd, 0
.endif
invoke DefSubclassProc, hWnd, uMsg, wParam, lParam
ret
TrackSubclass endp 

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_USER
invoke SetDlgItemTextW, hWnd, IDC_EDT1, addr msgBoxText
invoke MessageBoxW, 0, addr msgBoxText, addr msgBoxTitle, 0
.elseif uMsg == WM_CLOSE
invoke EndDialog, hWnd, 0
.endif
xor eax, eax
ret
DlgProc endp
end start


I'd like to note for anyone who comes through maybe looking for a similar answer, that WM_USER (0x400) through 0x7FFF is reserved for use by private window classes such as this - in other words, you have a lot of space for private window messages. GetAncestor functions really well, I am just using GA_PARENT because I thought it was an issue with my code before I figured out what was wrong - GA_ROOT would work as well in my case because the dialog window is the root, I just didn't catch that at the time (it's late :P).

An obvious annoyance from my code is that it does not notify you when the textbox first receives focus (i.e. starting up the application), only when it is changed to from something else (alt-tab, or switch from another element I'd assume but I am only working with one and haven't tested).

suggested byTight_Coder_Ex: use BN_CLICKED.

I've tried to use BN_CLICKED, and am having an issue. The documentation/references to it are sparse or confusing, for me, so I tried figuring out what was going on.
.elseif uMsg == WM_COMMAND
mov eax, wParam
; is LOWORD our edit control?
.if ax == IDC_EDT1
; what notification? were we clicked?
ror eax, 16
.if ax == BN_CLICKED
invoke MessageBoxW, 0, addr msgBoxText, addr msgBoxTitle, 0
.endif
.endif


Yet I never am notified of the click. I might be misinterpreting the documentation though, or this may not work for edit controls, but nonetheless I tried it like Tight_Coder_Ex suggested. Just in case anyone cares, here's the relevant documentation.
BTN_CLICKED Notification code
Button Messages

"When the user clicks a button, its state changes, and the button sends notification codes, in the form of WM_COMMAND messages, to its parent window. For example, a push button control sends the BN_CLICKED notification code whenever the user chooses the button. In all cases (except for BCN_HOTITEMCHANGE), the low-order word of the wParam parameter contains the control identifier, the high-order word of wParam contains the notification code, and the lParam parameter contains the control window handle."

Since this isn't a button though, it could be. I'd rather Windows just tell me something was clicked instead of having to check for different types of messages all day.
I looked up edit controls and found the documentation for it.
Apparently I can check for EN_SETFOCUS (suggested by japeth, but I didn't notice that until after searching for a while).

and thus, I went from all that code above, to simply:

include masm32rt.inc
include advapi32.inc
include x:\masm32\macros\ucmacros.asm

includelib advapi32.lib

DlgProc proto hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.data
WSTR msgBoxText, "You clicked it. Are you happy?"
WSTR msgBoxTitle, "Okay?"
.data?
hInst HINSTANCE ?
.const
IDD_DLG1                equ 1000
IDC_EDT1                equ 1001

.code
start:
invoke GetModuleHandle, NULL
mov hInst, eax
invoke DialogBoxParam, hInst, IDD_DLG1, NULL, addr DlgProc, NULL
invoke ExitProcess, eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_COMMAND
mov eax, wParam
; is LOWORD our edit control?
.if ax == IDC_EDT1
; what notification? were we clicked?
ror eax, 16
.if ax == EN_SETFOCUS
invoke MessageBoxW, 0, addr msgBoxText, addr msgBoxTitle, 0
.endif
.endif
.elseif uMsg == WM_CLOSE
invoke EndDialog, hWnd, 0
.endif
xor eax, eax
ret
DlgProc endp
end start


Thanks a ton, everyone! sorry for the huge wall of text.

*note* Should have looked up edit controls from the get-go, but I was convinced there must be an easier, generic way to get this information instead of using these specific messages. Guess not! ;P

donkey

...
mov hInst, eax ;get our handle
invoke DialogBoxParam, hInst, IDD_DLG1, NULL, addr DlgProc, NULL
invoke EnumChildWindows, hInst, addr EnumChildren, addr TrackSubclass
...


The EnumChildWindows call should have been in the WM_INITDIALOG handler, putting it after DialogBoxParam only succeeds in setting up the subclassing after all the windows are destroyed. Also it requires a handle to the main dialog not hInst in the first parameter. The way you call it it will fail, sorry I would have mentioned it but I thought it was obvious.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

disintx

Quote from: donkey on January 12, 2011, 02:35:20 PM
...
mov hInst, eax ;get our handle
invoke DialogBoxParam, hInst, IDD_DLG1, NULL, addr DlgProc, NULL
invoke EnumChildWindows, hInst, addr EnumChildren, addr TrackSubclass
...


The EnumChildWindows call should have been in the WM_INITDIALOG handler, putting it after DialogBoxParam only succeeds in setting up the subclassing after all the windows are destroyed. Also it requires a handle to the main dialog not hInst in the first parameter. The way you call it it will fail, sorry I would have mentioned it but I thought it was obvious.

Ah, I see.
It's a bit late where I am, and I came home with a strong buzz going so a lot went over my head. Thanks for the correction, though!

donkey

Quote from: disintx on January 12, 2011, 02:40:02 PM
Ah, I see.
It's a bit late where I am, and I came home with a strong buzz going so a lot went over my head. Thanks for the correction, though!

No problem. I've done that many times as well. Actually write some inspired code when I have a buzz on only to wake up and find a hundred lines of nonsense, damn gremlins rewriting my code while I'm sleeping....
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

disintx

Quote from: donkey on January 12, 2011, 03:05:15 PM
No problem. I've done that many times as well. Actually write some inspired code when I have a buzz on only to wake up and find a hundred lines of nonsense, damn gremlins rewriting my code while I'm sleeping....

:)

In case anyone cares/doesn't know, here's the relevant DialogProc

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_INITDIALOG
invoke EnumChildWindows, hWnd, addr EnumChildren, addr TrackSubclass
.elseif uMsg == WM_APP
.if bOnce == FALSE
mov bOnce, TRUE
invoke MessageBoxW, 0, addr msgBoxText, addr msgBoxTitle, 0
.endif
.elseif uMsg == WM_CLOSE
invoke EndDialog, hWnd, 0
.endif
xor eax, eax
ret
DlgProc endp


I have put a simple boolean check to see if it's been done before, otherwise I'd be spammed with message boxes all day long.
Cheers to all who helped!

*edit* I also switched to WM_APP as it's more recommended for this as apparently some messages from WM_USER can overlap with other controls (from what I read, not tested)