Tutorial GUIRegisterMsg

From AutoIt Wiki
Revision as of 11:06, 11 July 2015 by Rt01 (talk | contribs) ($iParam seems to be a typo. It uses $lParam for the rest of the tutorial.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Here is a short tutorial on GUIRegisterMsg - one of the more mysterious functions within AutoIt for newcomers. Purists might quibble over details here, but it is good enough for hobbyists like me!

Windows works by sending messages to everything on the system, so that everything knows what is going on. By messages, I mean things like the $GUI_EVENT_CLOSE we look for when we exit a GUI - but the possible range is much, much wider; change of focus, change of state, mouseclicks, mousemoves, key presses, etc, etc, etc.

GUIRegisterMsg allows us to intercept certain of these messages as they are sent by the GUIs we have created, so we can see who sent what. So when you see a GUIRegisterMsg($WM_COMMAND, "MY_WM_COMMAND") line in a script, this translates as "Whenever a WM_COMMAND message is sent by my GUI, please intercept it and pass it through the MY_WM_COMMAND function in this script". The actual message title is denoted by the constant $WM_COMMAND which is stored in WindowsConstant.au3 include file. Of course, you can use other message types as well (such as $WM_NOTIFY) - and call your function whatever you wish!

The function that you use GUIRegisterMsg to call MUST have these 4 parameters - $hWnd, $iMsg, $wParam, $lParam - because that is what Windows uses itself to pass the messages around. In the majority of cases, these parameters relate to the following:

  • $hWnd - the GUI which sent the message
  • $iMsg - the code for the message sent
  • $wParam, $lParam - details of what exactly the message is about. This varies according to the message and details can be found in MSDN but often these parameters will hold the identity of control that has been used and the exact version of the message that has been sent.

Let us look at some examples:


 GUIRegisterMsg($WM_COMMAND, "MY_WM_COMMAND")
 ;
 Func MY_WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)
 ;
     Local $iIDFrom = BitAND($wParam, 0xFFFF) ; LoWord - this gives the control which sent the message
     Local $iCode = BitShift($wParam, 16)     ; HiWord - this gives the message that was sent
     If $iCode = $EN_CHANGE Then ; If we have the correct message
         Switch $iIDFrom ; See if it comes from one of the inputs
             Case $hInput1
                 ; Do something
             Case $hInput2
                 ; Do something else
         EndSwitch
     EndIf
 ;
 EndFunc ;==>MY_WM_COMMAND

Here we are looking to see if the contents of two Input controls have been altered. We first intercept the $WM_COMMAND messages from our GUI using GUIRegisterMsg. We can ignore the GUI and the main message in this case - $WM_COMMAND is a huge set and as we are interested in the control we need not worry about the GUI - if we correctly identify the control, it can only come from our GUI! But in other cases, this is vital information, as you can imagine.

To see if an Input has been altered, we need to look for the $EN_CHANGE submessage and as you can see we need to investigate the contents of $wParam to determine this - and then determine the identity of the Input control which is also obtained from $wParam (MSDN is the bible here). The Bit* operators allow us to extract these values:


 Local $iIDFrom = BitAND($wParam, 0xFFFF) ; LoWord - this gives the ControlID of the control which sent the message 
 Local $iCode = BitShift($wParam, 16)     ; HiWord - this gives the submessage that was sent

We now know which control sent the message and what the sub-message was - so we check if it matches what we are looking for and if it does then we do whatever we need to do:

Another example:


 GUIRegisterMsg($WM_NOTIFY, "MY_WM_NOTIFY")
 ;
 Func MY_WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
 ;
 	Local $tNMHDR = DllStructCreate("int;int;int", $lParam)
 	If @error Then Return
 	If DllStructGetData($tNMHDR, 1) = $hLV_Handle Then ; Is it our ListView
 		If DllStructGetData($tNMHDR, 3) = $NM_DBLCLK Then $fDblClk = True ; Was it a double click
 	EndIf
 	$tNMHDR = 0 ; This not strictly necessary but does not hurt!
 ;
 	Return $GUI_RUNDEFMSG ; This tells AutoIt to process the message itself
 ;
 EndFunc   ;==>MY_WM_NOTIFY

This time we are trapping the $WM_NOTIFY message which lets us know about events like mouse clicks. In particular we are looking for a double click on a ListView within our GUI. As above we use GUIRegisterMsg to intercept the message. Again we can ignore the GUI and main message and just look for the control and submessage. In this case we need to do a bit more work on the $lParam parameter which is actually a Struct. However, AutoIt again makes it easy to obtain the handle of the control and the actual submessage - if these are the correct ones, we set a flag to True. Why, well keep reading and you will find out!

When we have finished dealing with the message we have intercepted, it gets passed along the chain of all the other message handlers so all the other applications in the system can see if they are interested too. But in some cases you might actually want to stop the message going further - I wrote a UDF to prevent the system adding the dotted lines around the control when it has focus and passing the message on was exactly what I was trying to prevent! So in the UDF I added a Return line to the function to stop it dead - just as here. Returning the $GUI_RUNDEFMSG constant tells AutoIt to run its own internal message handler.

Now, what about the use of a flag in the second example? Well, if you read the Help file for GUIRegisterMsg you will see the following:

"Warning: blocking of running user functions which execute Windows messages with commands such as "Msgbox()" can lead to unexpected behavior, the return to the system should be as fast as possible !!!"

Basically, if you spend too long in your message handling function, the rest of the system gets behind and becomes unstable. So you should get out of it as quickly as you can - and certainly never create anything that waits for user input.

There are 2 ways we can still run something long and complicated but still leave the handler quickly: either set a flag or action a dummy control. If we set a flag inside the handler, we can then look for it within our idle loop and run our blocking code from here without affecting the handler at all. If we action a dummy control then we do not even have to look for the flag! Here is an example of both methods to show you how to apply them.


 #include <GUIConstantsEx.au3>
 #include <WindowsConstants.au3>
 
 ; Whichever method we use, we need to declare the dummy control or the flag as a Global variable
 Global $hLeftClick, $fRightClick = False
 
 GUICreate("Click me!")
 
 ; Create a dummy control for the handler to action
 $hLeftClick = GUICtrlCreateDummy()
 
 GUISetState()
 
 ; Register our messages
 GUIRegisterMsg($WM_LBUTTONUP, "_WM_LBUTTONUP")
 GUIRegisterMsg($WM_RBUTTONUP, "_WM_RBUTTONUP")
 
 While 1
     Switch GUIGetMsg()
         Case $GUI_EVENT_CLOSE
             ExitLoop
         Case $hLeftClick
             ; Our dummy control was actioned so run the required code
             MsgBox(0, "Click", "LEFT CLICK!")
     EndSwitch
 
     ; Look for the flag
     If $fRightClick = True Then
         ; Run the code
         MsgBox(0, "Click", "RIGHT CLICK!")
         ; Do not forget to reset the flag!
         $fRightClick = False
     EndIf
 
 WEnd
 
 Func _WM_LBUTTONUP($hWnd, $iMsg, $wParam, $lParam)
     ; Action the dummy control
     GUICtrlSendToDummy($hLeftClick)
 EndFunc
 
 Func _WM_RBUTTONUP($hWnd, $iMsg, $wParam, $lParam)
     ; Set the flag
     $fRightClick = True
 EndFunc

I hope this short tutorial has made GUIRegisterMsg a little less complicated. Now I suggest you go and look at the many examples on the forum to see how they work and how each differs slightly depending on exactly the author is trying to do.