Jump to content

Registering and using WM_NOTIFY


Recommended Posts

Hi.  Attempting to catch and process a keystroke in a GUI window.  Here's the code:

; ----------------------------------------------------------------------
; | Library includes
; ----------------------------------------------------------------------
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GUIListView.au3>
#include <WinAPISys.au3>
#include <WinAPIvkeysConstants.au3>
#include <GuiButton.au3>


global $g_MainGUI
CreateMainGUI()


; ----------------------------------------------------------------------
func CreateMainGUI()

    $g_MainGUI = GUICreate("Test", 600, 600, -1, -1)

;   $b = _GUICtrlButton_Create($g_MainGUI, "Foo", 10, 10, 100, 30)   ; <--- Line #21
    $lv = _GUICtrlListView_Create($g_MainGUI, "", 50, 50, 100, 100)  ; <--- Line #22

    GUISetState(@SW_SHOW, $g_MainGUI)
    GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")

    do
        ;
    until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete()

endfunc


; ----------------------------------------------------------------------
func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)

    local $hdr   = DllStructCreate($tagNMHDR, $lParam)
    local $from  = HWnd(DllStructGetData($hdr, "hWndFrom"))
    local $id    = DllStructGetData($hdr, "IDFrom")
    local $event = DllStructGetData($hdr, "Code")

    ; Exit on [ctrl-Q]
    if ($event = $LVN_KEYDOWN) then
        $info = DllStructCreate($tagNMLVKEYDOWN, $lParam)
        $vkey = DllStructGetData($info, "VKey")
        if ($vkey = 0x51) and BitAND(_WinAPI_GetKeyState($VK_CONTROL), 0x8000) then
            ConsoleWrite('[ctrl-Q]' & @CRLF)  ; <--- Line #48
            _SendMessage($g_MainGUI, $GUI_EVENT_CLOSE)
        endif
    endif

    return $GUI_RUNDEFMSG
endfunc

There are two things about this code that don't work the way I expected, and I've been unable to figure out why:

1)

As it stands, the code creates a GUI window, then a ListView.  If I run it then immediately press ctrl-Q, the keystroke triggers WM_NOTIFY().  I know that because if I run in SciTE, '[ctrl-Q]' appears in the console window, so the ConsoleWrite() call on line #48 must have executed.

If I comment out the _GUICtrlListView_Create() call on line #22 and uncomment line #21 so that a Button is created instead, then run it, WM_NOTIFY() is never triggered.

Why the difference?  Doesn't the GUIRegisterMsg() call register WM_NOTIFY on behalf of the parent GUI?  Why does it work with one control type, but not the other?

2)

In the case with the ListView control, where it does catch the ctrl-Q, it executes _SendMessage($g_MainGUI, $GUI_EVENT_CLOSE).  In my simple mind, I'd have thought that would send the $GUI_EVENT_CLOSE message to the main GUI and cause it to exit the do/until loop.

But evidently not?  Because it doesn't.

Thanks in advance.  Just when I think I understand ...

/John

Link to comment
Share on other sites

1)  Not all messages are sent/received to all controls.  WM_NOTIFY has its own particularities.  It is by experimenting that you will learn which messages transit in windows.  You can also use a message catcher to grab all the messages directed to a window.  There are multiple examples made here.

2) The right message is $WM_CLOSE. (MSDN is the bible). There is also a list in appendix of help File. 

Edited by Nine
Link to comment
Share on other sites

1) You are catching only specific ListView's message

if ($event = $LVN_KEYDOWN)

LVN_xxx - means ListView Notification message

 

That's why it will not catch button message, there are different Buttons specific notification messages --> BN_CLICKED, BN_DBLCLK, ... (see "c:\Program Files\AutoIt3\Include\ButtonConstants.au3" )

Edited by Zedna
Link to comment
Share on other sites

Thank you both for your help.  I've got much to learn ...

Actually, for the specific thing I was trying to do (simply exit when ctrl-Q is hit), I found I could rather easily accomplish it with HotKeySet().  But I'm still the sort of person who wonders why something didn't work that I thought would, even if I don't necessarily need it anymore.  (That's how we learn.)

Zedna:  I didn't realize that the LVN prefix meant ListView Notification -- somehow missed that.  That will definitely help to know.

Nine:  By ' You can also use a message catcher to grab all the messages directed to a window', are you thinking of _WinAPI_SetWindowsHookEx()?

Last comment:  I thought of $WM_CLOSE.  In fact, getting a window to close isn't too hard (the HotKeySet() solution simply Exit's).  But I thought the 'right' (in some sense, cleaner) way to do it would be to somehow get the $GUI_EVENT_CLOSE to the GUI, so that the do/until loop would terminate, and then the script would exit properly through the end.  I guess I still don't know why it doesn't work.

Thanks again!

Link to comment
Share on other sites

2 minutes ago, DrJohn said:

are you thinking of _WinAPI_SetWindowsHookEx()?

No I meant a program that will monitor message transitions in a GUI.  As for example :

 

 

12 minutes ago, DrJohn said:

I guess I still don't know why it doesn't work.

It is because $GUI_EVENT_CLOSE is not a message nor a notification (it is an internal GUI event).  By sending $WM_CLOSE, the GUI will react to it and generate the $GUI_EVENT_CLOSE event.

Link to comment
Share on other sites

Thank you again.  This really helps my understanding!

As I said, I've got a lot to learn.  (I see that my profile even says I'm a 'Seeker'.  It's like it knows me ...)

(FWIW, I should have intuited that $GUI_EVENT_CLOSE is not a Windows message.  That was kind of dense on my part.)

I appreciate your insight.

/John

Edited by DrJohn
Link to comment
Share on other sites

Hi again Nine.  If you'd be willing to push this a step further:

You are right.  Sending $WM_CLOSE does cause the GUI to receive $GUI_EVENT_CLOSE, and the do/until loop terminates as expected.  Cool!

So here's a similar example, but this one uses $WM_KEYDOWN instead:

; ----------------------------------------------------------------------
; | Library includes
; ----------------------------------------------------------------------
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GUIListView.au3>
#include <WinAPISys.au3>
#include <WinAPIvkeysConstants.au3>


global $g_MainGUI
CreateMainGUI()


; ----------------------------------------------------------------------
func CreateMainGUI()

    $g_MainGUI = GUICreate("Test", 600, 600, -1, -1)
;   $lv = _GUICtrlListView_Create($g_MainGUI, "", 50, 50, 100, 100)  ; <--- Line #19

    GUISetState(@SW_SHOW, $g_MainGUI)
    GUIRegisterMsg($WM_KEYDOWN, "WM_KEYDOWN")

    do
        ;
    until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete()

endfunc


; ----------------------------------------------------------------------
func WM_KEYDOWN($hWnd, $iMsg, $wParam, $lParam)

    if ($wParam = 0x51) and BitAND(_WinAPI_GetKeyState($VK_CONTROL), 0x8000) then
        _SendMessage($g_MainGUI, $WM_CLOSE)
    endif

    return $GUI_RUNDEFMSG
endfunc

This works just as expected, if the _GUICtrlListView_Create() on line #19 is commented out.  The GUI detects the $WM_KEYDOWN event, WM_KEYDOWN() sends a $WM_CLOSE, and the GUI exits.

Uncomment line #19 so that a ListView is created, and it no longer works that way.  Is it reasonable to assume the ListView is somehow catching and eating the $WM_KEYDOWN message before the main GUI can see it?

(I have downloaded and taken a quick look at Larsj's Windows Message Monitor, and I suspect using it, I can find out ...)

/John

Link to comment
Share on other sites

Follow-up:

Using the Windows Message Monitor, it seems I can verify that in the version that creates the ListView, when I hit ctrl-Q it is the Listview that receives the $WM_KEYDOWN message, rather than the main GUI.

I guess what surprises me is that the registered function WM_KEYDOWN() doesn't get called all the same, since the ListView is a child control of the main GUI.

Image 1.png

Link to comment
Share on other sites

Glad you sort it out.  Yes it is the ListView that receives the $WM_KEYDOWN message.  You can register message at control level either using _WinAPI_SetWindowLong or with the Win32 API SetWindowSubclass (google the last in MSDN).

There is a good example of using _WinAPI_SetWindowLong in help file under _WinAPI_CallWindowProc (this approach I recommend as you have more documentation of how to implement it in AutoIt)

Link to comment
Share on other sites

Look at GUISetAccelerators() which is exactly what you need for your hotkey: Ctrl+Q = Exit

This way it will work with and/or without ListView :-)

https://www.autoitscript.com/autoit3/docs/functions/GUISetAccelerators.htm

 

EDIT:

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GUIListView.au3>
#include <WinAPISys.au3>

global $g_MainGUI
CreateMainGUI()

func CreateMainGUI()

    $g_MainGUI = GUICreate("Test", 600, 600, -1, -1)
    $idUserExit = GUICtrlCreateDummy()
 ;   $lv = _GUICtrlListView_Create($g_MainGUI, "", 50, 50, 100, 100)  ; <--- Line #19

    Local $aAccelKeys[1][2] = [["^q", $idUserExit]]
    GUISetAccelerators($aAccelKeys)

    GUISetState(@SW_SHOW, $g_MainGUI)

    do
        $msg = GUIGetMsg()
        If $msg = $idUserExit Then _SendMessage($g_MainGUI, $WM_CLOSE)
        
    until $msg = $GUI_EVENT_CLOSE
    GUIDelete()

endfunc

 

Edited by Zedna
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...