[CLASS] MouseTracker - Track mouse on GUIs/Controls

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
cyruz
Posts: 346
Joined: 30 Sep 2013, 13:31

[CLASS] MouseTracker - Track mouse on GUIs/Controls

26 Feb 2019, 21:37

Hi guys,

the following class tries to standardize mouse tracking on GUIs and Controls.

I started it using a combination of WM_MOUSEMOVE - WM_MOUSEHOVER - WM_MOUSELEAVE messages, but I soon realized that WM_MOUSEHOVER is just adding complexity to the code, without real advantages for a simple tracking, so I ditched it and relied on WM_MOUSEMOVE to detect when the mouse enters a GUI/Control.

Because of this the class doesn't pretend to be a full fledged solution to manage the before-mentioned messages, but just a simple tracker for GUI/Control mouse "enter" and "leave" events.

Code: Select all

; ----------------------------------------------------------------------------------------------------------------------
; Name .........: MouseTracker class library
; Description ..: Track mouse when entering and leaving GUIs/Controls.
; AHK Version ..: AHK_L 1.1.30.01 x32/64 ANSI/Unicode
; Author .......: cyruz - http://ciroprincipe.info
; License ......: WTFPL - http://www.wtfpl.net/txt/copying/
; Changelog ....: Feb. 24, 2019 - v0.1   - First version.
; ..............: Feb. 27, 2019 - v0.2   - Implemented multi handle tracking.
; ..............: Feb. 27, 2019 - v0.2.1 - Fixed issue with WM_MOUSEMOVE events not flagged as managed.
; Remarks ......: Uses the following functions, structures and messages from Win32 API.
; ..............: "WM_MOUSEMOVE" Win32 message:
; ..............: https://docs.microsoft.com/en-us/windows/desktop/inputdev/wm-mousemove
; ..............: "WM_MOUSELEAVE" Win32 message:
; ..............: https://docs.microsoft.com/en-us/windows/desktop/inputdev/wm-mouseleave
; ..............: "TrackMouseEvent" Win32 function:
; ..............: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
; ..............: "TRACKMOUSEEVENT" Win32 structure:
; ..............: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagtrackmouseevent
; ----------------------------------------------------------------------------------------------------------------------

/*

* Simple usage example:
-----------------------

	#Include <MouseTracker>
    Global logStr

	Gui, +LastFound
	Gui, Add, Edit, vLog1 +HWNDhLog1 w500 r40
	Gui, Show, AutoSize x10 y10

	MouseTracker.Track([hLog1, Func("cbME"), ""])
	Return

	GuiClose()
	{
		MouseTracker.UnTrack()
		ExitApp
	}

	cbME( aGui, aGuiCtrl, wParam, lParam, Msg, hWnd )
	{
		logStr .= "MOUSE ENTER: " hWnd "`n"
		GuiControl,, Log1, %logStr%
		MouseTracker.Track([hWnd, "", Func("cbML")])
	}

	cbML( aGui, aGuiCtrl, wParam, lParam, Msg, hWnd )
	{
		logStr .= "MOUSE LEAVE: " hWnd "`n"
		GuiControl,, Log1, %logStr%
		MouseTracker.Track([hWnd, Func("cbME"), ""])
	}


* Complex usage example:
------------------------

	#Include <MouseTracker>
    Global logStr, hGui, hEntered
	
	Gui, +HWNDhGui
	Gui, Add, Edit,     vLog1 +HWNDhLog1 w500 r40
	Gui, Add, Progress, vPro1 +HWNDhPro1 w248 h100 BackgroundFF0000
	Gui, Add, Progress, vPro2 +HWNDhPro2 w248 h100 x+2 BackgroundFF0000
	Gui, Show, AutoSize x10 y10

	MouseTracker.Track([hPro1, Func("cbME"), ""], [hPro2, Func("cbME"), ""])
	Return

	GuiClose()
	{
		MouseTracker.UnTrack()
		ExitApp
	}

	cbME( aGui, aGuiCtrl, wParam, lParam, Msg, hWnd )
	{
		logStr .= "MOUSE ENTER: " hWnd "`n"
		GuiControl,, Log1, %logStr%

		GuiControlGet, %aGuiCtrl%, Pos
        VarSetCapacity(POINT, 8, 0)
        NumPut(%aGuiCtrl%x, POINT, 0, "Int")
        NumPut(%aGuiCtrl%y, POINT, 4, "Int")
        DllCall( "User32.dll\ClientToScreen", Ptr,hGui, Ptr,&POINT )

		Gui, OV: +Owner%hGui% -Caption +Toolwindow
		Gui, OV: Margin, 0, 0
		Gui, OV: Add, Progress, +HWNDhPro3 w248 h100 Background0000FF
		Gui, OV: Show, % "w" 248 " h" 100 " x" NumGet(POINT, 0, "Int") " y" NumGet(POINT, 4, "Int") NA

		hEntered := hWnd
		MouseTracker.Track([hPro3, "", Func("cbML")])
	}

	cbML( aGui, aGuiCtrl, wParam, lParam, Msg, hWnd )
	{
		logStr .= "MOUSE LEAVE: " hWnd "`n"
		GuiControl,, Log1, %logStr%

		Gui, OV: Destroy

		MouseTracker.Track([hEntered, Func("cbME"), ""])
	}

*/

; Name .........: MouseTracker - PUBLIC STATIC CLASS
; Description ..: Manages mouse tracking on GUIs and Controls through WM_MOUSEMOVE and WM_MOUSELEAVE.
Class MouseTracker
{
	Static WM_MOUSEMOVE         := 0x0200
	     , WM_MOUSELEAVE        := 0x02A3
		 , FN_MOUSEENTER        := "__MT_MOUSEMOVE"
         , FN_MOUSELEAVE        := "__MT_MOUSELEAVE"
	     , TME_LEAVE            := 0x00000002
		 , TME_CANCEL           := 0x80000000
	     , TME_CBSIZE_OFFT      := 0
	     , TME_DWFLAGS_OFFT     := 4
	     , TME_HWNDTRACK_OFFT   := 8
	     , TME_DWHOVERTIME_OFFT := A_PtrSize == 8 ? 16 : 12
	     , TME_CBSIZE           := A_PtrSize == 8 ? 24 : 16
		 , TME_DWHOVERTIME      := 1 ; Arbitrary value.
		 , FLAGS_CANCEL_LEAVE   := 0x80000000|0x00000002i
		 , TIMER_SAFE_VALUE     := Abs(StrReplace(A_BatchLines, "ms"))+1

	; Disallow instantiation.
	__New( )
	{
		Return False
	}

    ; Name .........: Track - PUBLIC STATIC METHOD
    ; Description ..: Initialize mouse tracking on the desired handle.
    ; Parameters ...: arrTracking* = Variadic parameter accepting a series of request arrays.
	; ..............: Request array form is: [ hWnd, cbEnter, cbLeave ]
	; ............... hWnd    = Handle to the GUI or Control to be checked for mouse tracking.
	; ..............: cbEnter = Callback - Fires when the mouse is hovering the desired handle.
	; ..............: cbLeave = Callback - Fires when the mouse has left the desired handle.
	; ..............:                      Fires if mouse is not on the handle when called.
	; Remarks ......: The functions used as callbacks must accept 6 parameters:
	; ..............: A_Gui, A_GuiControl, wParam, lParam, Msg, hWnd.
	; ..............: For info about those parameter, please check "OnMessage" in AutoHotkey documentation.
	; ..............: Tracking lasts only for one event, it must be reinstantiated in the callbacks if required.
	; ..............: Please keep tracking reinstantiation code as last line in the callback to avoid issues.
	Track( arrTracking* )
	{
		; Initialize memory and requests object during first call.
		If ( !MouseTracker.p_TME )
		{
			If ( (MouseTracker.p_TME := DllCall("LocalAlloc", UInt,0x0040, UInt,MouseTracker.TME_CBSIZE)) == "" )
				Return False
		    NumPut(MouseTracker.TME_CBSIZE,      MouseTracker.p_TME+0, MouseTracker.TME_CBSIZE_OFFT,      "UInt")
		  , NumPut(MouseTracker.TME_DWHOVERTIME, MouseTracker.p_TME+0, MouseTracker.TME_DWHOVERTIME_OFFT, "UInt")
		  , MouseTracker.Requests := {}		
		}

		Loop % arrTracking.Length()
		{
			; Get single request array from parameters array:
			; arrRequest[1] = hWnd GUI/Control.
			; arrRequest[2] = Mouse Enter Callback.
			; arrRequest[3] = Mouse Leave Callback.
			arrRequest := arrTracking[A_Index]

			; Create the request object, available globally, if not present. Request is identified by the hWnd:
			; { hWnd : { "cb_ME" : Callback_Mouse_Enter , "cb_ML" " Callback_Mouse_Leave } }
			If ( !MouseTracker.Requests[arrRequest[1]] )
				MouseTracker.Requests[arrRequest[1]] := { "cb_ME":0, "cb_ML":0 }

			; Track mouse leave. We implemented it monitoring the WM_MOUSELEAVE message. This 
			; kind of tracking must be requested with the "TrackMouseEvent" Win32 system call.
			If ( arrRequest[3] != "" ) ; arrRequest[3] = Mouse Leave Callback.
			{
				; Cancel a previous tracking request if it's of the same type of the new one.
				If ( MouseTracker.Requests[arrRequest[1]].cb_ML )
					NumPut( arrRequest[1]
					      , MouseTracker.p_TME+0
						  , MouseTracker.TME_HWNDTRACK_OFFT
						  , "Ptr" )
				  , NumPut( MouseTracker.FLAGS_CANCEL_LEAVE
				          , MouseTracker.p_TME+0
						  , MouseTracker.TME_DWFLAGS_OFFT
						  , "UInt" )
				  , DllCall("TrackMouseEvent", Ptr,MouseTracker.p_TME)
				  , OnMessage(MouseTracker.WM_MOUSELEAVE, "")

			   ; Issue a new request with the desired tracking values.
			    NumPut( arrRequest[1]
			          , MouseTracker.p_TME+0
					  , MouseTracker.TME_HWNDTRACK_OFFT
					  , "Ptr")
			  , NumPut( MouseTracker.TME_LEAVE
			          , MouseTracker.p_TME+0
					  , MouseTracker.TME_DWFLAGS_OFFT
					  , "UInt" )
			  , DllCall("TrackMouseEvent", Ptr,MouseTracker.p_TME)

				; Add the Mouse Leave Callback to the request object.
			    MouseTracker.Requests[arrRequest[1]].cb_ML := arrRequest[3]
			  , b_ML := True ; Flag WM_MOUSELEAVE monitoring as required.
			}

			; Track mouse enter. We implemented it monitoring the WM_MOUSEMOVE message. This message
			; is normally sent to all windows when the mouse is hovering them. We ditched monitoring
			; of the WM_MOUSEHOVER message because its usage is quirky and prone to errors.
			If ( arrRequest[2] != "" ) ; arrRequest[2] = Mouse Enter Callback.
				MouseTracker.Requests[arrRequest[1]].cb_ME := arrRequest[2]
			  , b_MM := True ; Flag WM_MOUSEMOVE monitoring as required.
		}

		; Start monitoring if flagged as required.
		If ( b_ML )
			OnMessage(MouseTracker.WM_MOUSELEAVE, MouseTracker.FN_MOUSELEAVE)
		If ( b_MM )
			OnMessage(MouseTracker.WM_MOUSEMOVE,  MouseTracker.FN_MOUSEENTER)
			
		Return True
	}

    ; Name .........: UnTrack - PUBLIC STATIC METHOD
    ; Description ..: Disable all message management and free memory.
	UnTrack()
	{
		OnMessage(MouseTracker.WM_MOUSEMOVE,  "")
		OnMessage(MouseTracker.WM_MOUSELEAVE, "")
		DllCall("LocalFree", Ptr,MouseTracker.p_TME)
		MouseTracker.p_TME    := 0
		MouseTracker.Requests := ""
	}
}

; Name .........: __HT_MOUSEMOVE - PRIVATE FUNCTION
; Description ..: Manage WM_MOUSEMOVE message.
__MT_MOUSEMOVE( wParam, lParam, Msg, hWnd )
{
	If ( !MouseTracker.Requests[hWnd].cb_ME )
		Return

	OnMessage(MouseTracker.WM_MOUSEMOVE, "")
  , objBf := MouseTracker.Requests[hWnd].cb_ME.Bind(A_Gui, A_GuiControl, wParam, lParam, Msg, hWnd)
  , MouseTracker.Requests[hWnd].cb_ME := 0 ; Avoid entering this function again for the same hWnd.
	
	; Call the BoundFounc object after the function returned.
	SetTimer, % objBf, % -(MouseTracker.TIMER_SAFE_VALUE)

	Return
}

; Name .........: __HT_MOUSELEAVE - PRIVATE FUNCTION
; Description ..: Manage WM_MOUSELEAVE message.
__MT_MOUSELEAVE( wParam, lParam, Msg, hWnd )
{
	OnMessage(MouseTracker.WM_MOUSELEAVE, "")
  , objBf := MouseTracker.Requests[hWnd].cb_ML.Bind(A_Gui, A_GuiControl, wParam, lParam, Msg, hWnd)
  , MouseTracker.Requests[hWnd].cb_ML := 0 ; Important for the "previous tracking request" check.

	; Call the BoundFounc object after the function returned.
	SetTimer, % objBf, % -(MouseTracker.TIMER_SAFE_VALUE)

	Return
}

Test code is in the example section of the class.


Changelog:
  • 2019.02.27 - Added multi handle tracking capabilities, modified Track() parameters.
  • 2019.02.27 - Fixed issue with WM_MOUSEMOVE, events were not flagged as managed.

Cheers :beer:
Last edited by cyruz on 27 Feb 2019, 11:04, edited 9 times in total.
ABCza on the old forum.
My GitHub.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 03:42

i dont understand why ure doing this:

Code: Select all

...
If ( (MouseTracker.p_TME := DllCall("LocalAlloc", UInt,0x0040, UInt,MouseTracker.TME_SIZE)) == "" )
...
what is the purpose of LocalAlloc, why not just VarSetCapacity. also is MouseTracker.TME_SIZE defined anywhere?
ur examples crash sporadically with 0xc0000374 STATUS_HEAP_CORRUPTION.
1.1.30.01 w64 on win10 x64
User avatar
cyruz
Posts: 346
Joined: 30 Sep 2013, 13:31

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 04:06

swagfag wrote:
27 Feb 2019, 03:42
...
Hi swagfag, thanks for spotting that error. The correct var is TME_CBSIZE, I guess this was causing heap corruption, can you try again?

Regarding LocalAlloc, I just keep the pointer for freeing memory later. No any particular reason.
Last edited by cyruz on 27 Feb 2019, 08:32, edited 1 time in total.
ABCza on the old forum.
My GitHub.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 04:20

yes it appears to have fixed it
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 07:41

Looks good, thanks for sharing :thumbup: . I tried both examples it seem to work fine.
Minor comments,
For onmessage callbacks, you can use methods of the class instead of the functions if you like, eg,

Code: Select all

class t {
	g(){
		onmessage(0x201, t.f.bind(t)) ; left click iirc
	}
	f(a,b,c,d){
		msgbox % a "`n" b "`n" c "`n" d  "`n" (this == t)
	}
}
gui show, w200 h100
t.g()
Regarding LocalAlloc / varsetcapacity. If the pointer needs to be valid after the function returns you cannot use varsetcapacity on a local variable. Alternatively, you could use this.SetCapacity("p_TME", the_size) to allocate persistent memory stored in the object.

Cheers.
User avatar
cyruz
Posts: 346
Joined: 30 Sep 2013, 13:31

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 08:31

Helgef wrote:
27 Feb 2019, 07:41
...
Thanks, I'm trying to adapt it for multi handle tracking.

Regarding the OnMessage stuff, although it's mostly for aesthetics, I will implement it in the next release, because I really like it :mrgreen:


Cheers.
ABCza on the old forum.
My GitHub.
User avatar
cyruz
Posts: 346
Joined: 30 Sep 2013, 13:31

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 10:06

Ok, I added multi handle tracking. New version in the OP.

Regarding that change Helgef, I get an error in the boundfunc object inside the onmessage function if I implement it as in-class method. I'm trying to understand why.

EDIT: OOOPS, new version again, fixed a small but critical issue, as per changelog.
ABCza on the old forum.
My GitHub.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 11:38

no i mean couldnt he have just as easily went for:
nevermind, to do that ud need to copy the memory the struct occupies using winapi functions, direct assignment alone wont do

Code: Select all

VarSetCapacity(TME, sizeofTME, 0)
MouseEvent.TME := TME
; .... later
MouseEvent.TME := ""
or just Obj.SetCapacity("TME", sizeofTME), which u already showed (i didnt know u could do this)

the only times ive seen these XXXAlloc functions used was for the explicit purpose of XXXLocking them(eg when working with the Clipboard API, SetClipboardData() etc), which is why seeing it used here struck me as odd
Last edited by swagfag on 27 Feb 2019, 18:25, edited 1 time in total.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 14:24

That is not supported, obj.setcapacity is recommended. If you wanted to use varsetcapacity you would need to use a non-zero FillByte parameter, then manual null terminate and update string length with varsetcapacity(var, -1), that is ofc completely useless.

Cheers.
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

27 Feb 2019, 22:28

Congrats on the ultra clean code!

Thanks for sharing
Rafaews
Posts: 25
Joined: 16 Mar 2018, 21:19

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

06 Jul 2020, 16:02

Super cool! But, it doesn't seem to work with Text controls, right? Or am I doing something wrong?
kauan014
Posts: 55
Joined: 18 Feb 2021, 20:03

Re: [CLASS] MouseTracker - Track mouse on GUIs/Controls

23 Jan 2022, 11:12

Hi, just a doubt, theres no need to delete any previous SetTimer?
It isn't creating a new timer each time the code read these lines?

Code: Select all

; Call the BoundFounc object after the function returned.
SetTimer, % objBf, % -(MouseTracker.TIMER_SAFE_VALUE)

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 131 guests