[solved] OnMessage() monitoring in Class

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
Learning one
Posts: 173
Joined: 04 Oct 2013, 13:59
Location: Croatia
Contact:

[solved] OnMessage() monitoring in Class

02 Apr 2015, 19:07

Let's say a have a class and I want to monitor WM_LBUTTONDOWN message. It can be done like this (run the script, click&drag gui please and it will update x,y coords info):

Code: Select all

OnMessage(0x201, "WM_LBUTTONDOWN")
Test1 := new TestClass("Test1", 300, 400)
return

class TestClass {
	__New(Name, x, y) {
		Gui, New, +Hwndhwnd
		Gui %hwnd%: -Caption +AlwaysOnTop +ToolWindow +OwnDialogs
		Gui %hwnd%: Add, Text, x5 y5 w100 h20, % Name
		Gui %hwnd%: Add, Text, x5 y30 w100 h20 hwndhPos, % x ", " y
		Gui %hwnd%: Show, % "x" x " y" y " w110 h55 NA"
		this.hPos := hPos, this.hwnd := hwnd, this.Name := Name 
	}
}

WM_LBUTTONDOWN() {
	global Test1
	PostMessage, 0xA1, 2
	KeyWait, LButton
	WinGetPos, x,y,,, % "ahk_id " Test1.hwnd
	GuiControl, % Test1.hwnd ":", % Test1.hPos, % x ", " y
}
It's not the best way, but never mind... What I would like to know is how to include that WM_LBUTTONDOWN message monitoring in the class itself? Below is a not working code, but it will give you idea of what I would like to do.

Code: Select all

Test1 := new TestClass("Test1", 300, 400)
;Test2 := new TestClass("Test2", 450, 400)
return

class TestClass {
	__New(Name, x, y) {
		Gui, New, +Hwndhwnd
		Gui %hwnd%: -Caption +AlwaysOnTop +ToolWindow +OwnDialogs
		Gui %hwnd%: Add, Text, x5 y5 w100 h20, % Name
		Gui %hwnd%: Add, Text, x5 y30 w100 h20 hwndhPos, % x ", " y
		Gui %hwnd%: Show, % "x" x " y" y " w110 h55 NA"
		this.hPos := hPos, this.hwnd := hwnd, this.Name := Name 
		OnMessage(0x201,  this["WM_LBUTTONDOWN"])	; my intention: I want to monitor WM_LBUTTONDOWN message by WM_LBUTTONDOWN method
	}
	WM_LBUTTONDOWN() {
		ToolTip % "LButton down on " this.Name	; I would like to see: "LButton down on Test1" but I see just "LButton down on "
		PostMessage, 0xA1, 2
		KeyWait, LButton
		ToolTip
		WinGetPos, x,y,,, % "ahk_id " this.hwnd					; doesn't work. "this" obviously doesn't refer to derived object?
		GuiControl, % this.hwnd ":", % this.hPos, % x ", " y	; doesn't work. "this" obviously doesn't refer to derived object?
	}
}
I would also like to have some check like
if (A_Gui = hwnd of any window which belongs to object derived from TestClass)
--> do stuff...
else
--> this is a window which doesn't belong to object derived from TestClass - do nothing, don't interfere
I also tried to use WM_LBUTTONDOWN(wParam, lParam, msg, hwnd) as a method instead of just WM_LBUTTONDOWN() (no params) but I can't register it via OnMessage() because it has 5 params; 4 + one hidden; "this"... and the limit for OnMessage is 4. My plan was to compare 4. parameter; hwnd with A_Gui and if they match, do stuff... - or is this nonsense - they will always match in WM_LBUTTONDOWN case?

Thanks in advance.
Last edited by Learning one on 08 Apr 2015, 15:06, edited 2 times in total.
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: OnMessage() monitoring in Class

02 Apr 2015, 19:37

Pass this.WM_LBUTTONDOWN.Bind(this) to OnMessage. Bind requires v1.1.20+.
but I can't register it via OnMessage() because it has 5 params
Even if you could register it, OnMessage still wouldn't pass the this parameter. So instead of passing the method, you pass a bound function which (when called) passes this for you.
User avatar
Learning one
Posts: 173
Joined: 04 Oct 2013, 13:59
Location: Croatia
Contact:

Re: OnMessage() monitoring in Class

03 Apr 2015, 04:34

Thank you Lexikos! I passed this.WM_LBUTTONDOWN.Bind(this) to OnMessage().
I also replaced Tooltipwith MsgBox, uncommented ;Test2 := new TestClass("Test2", 450, 400), and added 3. line: Test3 := new TestClass("Test3", 600, 400) so code now looks like this;

Code: Select all

Test1 := new TestClass("Test1", 300, 400)
Test2 := new TestClass("Test2", 450, 400)
Test3 := new TestClass("Test3", 600, 400)
return

class TestClass {
    __New(Name, x, y) {
        Gui, New, +Hwndhwnd
        Gui %hwnd%: -Caption +AlwaysOnTop +ToolWindow +OwnDialogs
        Gui %hwnd%: Add, Text, x5 y5 w100 h20, % Name
        Gui %hwnd%: Add, Text, x5 y30 w100 h20 hwndhPos, % x ", " y
        Gui %hwnd%: Show, % "x" x " y" y " w110 h55 NA"
        this.hPos := hPos, this.hwnd := hwnd, this.Name := Name
        OnMessage(0x201,  this.WM_LBUTTONDOWN.Bind(this))
    }
    WM_LBUTTONDOWN() {
        PostMessage, 0xA1, 2
        KeyWait, LButton
        WinGetPos, x,y,,, % "ahk_id " this.hwnd
        GuiControl, % this.hwnd ":", % this.hPos, % x ", " y
        MsgBox,,, % this.Name, 0.5
    }
}
Now, when I derived 3 objects from TextClass, if I click & drag any single Gui (which "belongs" to object derived from TextClass), MsgBox will show 3 times. At first, I was expecting it will show only once...
Should I rewrite WM_LBUTTONDOWN() method and "filter" that like this:

Code: Select all

;class TestClass {
    WM_LBUTTONDOWN() {
        PostMessage, 0xA1, 2
        KeyWait, LButton
		if (A_Gui = this.hwnd) {
			WinGetPos, x,y,,, % "ahk_id " this.hwnd
			GuiControl, % this.hwnd ":", % this.hPos, % x ", " y
			MsgBox,,, % this.Name, 0.5
		}
    }
;}
Or like this:

Code: Select all

;class TestClass {
    WM_LBUTTONDOWN(wParam, lParam, msg, hwnd) {
        PostMessage, 0xA1, 2
        KeyWait, LButton
		if (hwnd = this.hwnd) {
			WinGetPos, x,y,,, % "ahk_id " this.hwnd
			GuiControl, % this.hwnd ":", % this.hPos, % x ", " y
			MsgBox,,, % this.Name, 0.5
		}
    }
;}
Which one is OK? Do you suggest some other approach? Thank you.
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: OnMessage() monitoring in Class

03 Apr 2015, 21:43

Neither!

You called OnMessage(0x201, fn), which means "call fn whenever any window belonging to the script receives the message 0x201". If you call it three times with three different function objects, all three will be called whenever any window receives that message.

You do not want to post the 0xA1 message three times every time the button is clicked, do you?

If the WM_LBUTTONDOWN method is called for a particular GUI, presumably you only want to handle messages received by that GUI. So you should return immediately if hwnd != this.hwnd, before doing anything else. If you've handled the message, you should return an integer to let AutoHotkey know it doesn't need to call any other handlers.

The method will be called once for each GUI, and each one will have to check if it is the intended recipient. If there is usually only one GUI this is fine, but if there can be many it could be very inefficient. An alternative would be to register the message handler only once for that class (not for each instance), and have it resolve the GUI object by HWND.

Note that even if A_Gui is a HWND, A_Gui and the HWND parameter can differ if you clicked on a control which accepts mouse input.
User avatar
Learning one
Posts: 173
Joined: 04 Oct 2013, 13:59
Location: Croatia
Contact:

Re: OnMessage() monitoring in Class

05 Apr 2015, 21:29

Lexikos, thanks for your answer and sorry for my late reply. I'll have more more than one derived object and therefore more than one GUI too, so I registred message handler only once for that class, as you suggested. Is this code OK now? If not, could you please show me how would you do it - I'm running out of ideas what to do to do it right.

Code: Select all

Test1 := new TestClass("Test1", 300, 200)
Test2 := new TestClass("Test2", 450, 200)
Test3 := new TestClass("Test3", 600, 200)
return

class TestClass {
	static DerivedObjects := []
	static Dummy201 := OnMessage(0x201,  TestClass.WM_LBUTTONDOWN.Bind(TestClass))
	__New(Name, x, y) {
		Gui, New, +Hwndhwnd
		Gui %hwnd%: -Caption +AlwaysOnTop +ToolWindow +OwnDialogs
		Gui %hwnd%: Add, Text, x5 y5 w100 h20, % Name
		Gui %hwnd%: Add, Text, x5 y30 w100 h20 hwndhPos, % x ", " y
		Gui %hwnd%: Show, % "x" x " y" y " w110 h55 NA"
		this.hPos := hPos, this.hwnd := hwnd, this.Name := Name
		TestClass.DerivedObjects.Push(this)	; circular reference...
	}
	WM_LBUTTONDOWN() {		
		for k,obj in TestClass.DerivedObjects
		{
			if (obj.hwnd=A_Gui) {	; found
				PostMessage, 0xA1, 2
				KeyWait, LButton
				WinGetPos, x,y,,, % "ahk_id " obj.hwnd
				GuiControl, % obj.hwnd ":", % obj.hPos, % x ", " y
				return 1	; no need to call any other message handlers
			}				
		}
	}
}
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: OnMessage() monitoring in Class

06 Apr 2015, 03:08

What I would do in this case:

Code: Select all

Test1 := new TestClass("Test1", 300, 200)
Test2 := new TestClass("Test2", 450, 200)
Test3 := new TestClass("Test3", 600, 200)
return

TestClassEscape:
   %A_Gui% := ""
Return

^+q::
ExitApp

class TestClass {
   static GuiCount := 0
   static ClassGuis := []
   ; Store the BoundFunc object so you may remove the message handler
   static MessageFunc := ObjBindMethod(TestClass, "WM_LBUTTONDOWN")
   __New(Name, x, y) {
      Gui, %Name%:New, +Hwndhwnd, %Name%
      Gui, -Caption +AlwaysOnTop +ToolWindow +OwnDialogs +LabelTestClass
      Gui, Add, Text, x5 y5 w100 h20, % Name
      Gui, Add, Text, x5 y30 w100 h20 hwndhPos, % x ", " y
      Gui, Show, % "x" x " y" y " w110 h55 NA"
      this.hPos := hPos, this.hwnd := hwnd, this.Name := Name
      ; When the first instance will be created, activate the message handler
      if (this.base.GuiCount = 0)
         OnMessage(0x201, this.MessageFunc)
      ; Count the active instances
      this.base.GuiCount++
      this.base.ClassGuis[Name] := &this
   }
   __Delete() {
      Gui, % this.Name . ":Destroy"
      this.base.ClassGuis.Delete(this.Name)
      ; Count the active instances
      this.base.GuiCount--
      ; When the last instance will be destroyed, remove the message handler
      if (this.base.GuiCount = 0)
         OnMessage(0x201, this.MessageFunc, 0)
   }
   WM_LBUTTONDOWN() {
      if (obj := Object(this.ClassGuis[A_Gui])) {
         PostMessage, 0xA1, 2
         KeyWait, LButton
         WinGetPos, x, y, , , % "ahk_id " obj.hwnd
         GuiControl, %A_Gui%:, % obj.hPos, % x ", " y
         return 0    ; no need to call any other message handlers
      }
   }
}
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: OnMessage() monitoring in Class

06 Apr 2015, 21:22

Both seem adequate.

Having the GUI close itself when the script releases its reference to the GUI mightn't be appropriate. You could instead store the GUI object (as a normal, counted reference) in the array when the GUI is shown and remove it when the GUI is closed, but Destroy it only when the last reference is released (assuming the object provides some way to show the GUI again). So long as the GUI is visible, the user can interact with it and cause the WM_LBUTTONDOWN method to be called, even if there are no external references to the object. If you use this model, the circular reference isn't a problem as you want the GUI object to remain alive while the GUI is visible.

If you're storing the GUI objects in an array, you may as well use array[hwnd] := object to facilitate mapping HWND to object. Just be sure to use .Delete(hwnd) (v1.1.21+) or .Remove(hwnd, "") and not .Remove(hwnd).
User avatar
Learning one
Posts: 173
Joined: 04 Oct 2013, 13:59
Location: Croatia
Contact:

Re: [solved] OnMessage() monitoring in Class

08 Apr 2015, 16:12

Just me, Lexikos, thank you for your help! :)
I think I'll end up with something like this;

Code: Select all

Test1 := new TestClass("Test1", 300, 200)
Test2 := new TestClass("Test2", 450, 200)
Test3 := new TestClass("Test3", 600, 200)
return

class TestClass {
	static DerivedObjectsCount := 0, DerivedObjects := {}
	static LBUTTONDOWNFunc := ObjBindMethod(TestClass, "WM_LBUTTONDOWN")	; store the BoundFunc object so you may remove the message handler
	__New(Name, x, y) {
		Gui, New, +Hwndhwnd
		Gui %hwnd%: -Caption +AlwaysOnTop +ToolWindow +OwnDialogs
		Gui %hwnd%: Add, Text, x5 y5 w100 h20, % Name
		Gui %hwnd%: Add, Text, x5 y30 w100 h20 hwndhPos, % x ", " y
		Gui %hwnd%: Show, % "x" x " y" y " w110 h55 NA"
		this.hPos := hPos, this.hwnd := hwnd, this.Name := Name
		TestClass.DerivedObjectsCount += 1
		if (TestClass.DerivedObjectsCount > 0)
			OnMessage(0x201, this.LBUTTONDOWNFunc)	; activate the message handler
		TestClass.DerivedObjects[hwnd] := &this		; store object's address
	}
	WM_LBUTTONDOWN() {
		if (TestClass.DerivedObjects.HasKey(A_Gui)) { ; GUI which belongs to object derived from this class
			PostMessage, 0xA1, 2
			KeyWait, LButton
			obj := Object(TestClass.DerivedObjects[A_Gui])
			WinGetPos, x,y,,, % "ahk_id " obj.hwnd
			GuiControl, % obj.hwnd ":", % obj.hPos, % x ", " y
			return 0    ; no need to call any other message handlers          
		}
	}
	__Delete() {
		;MsgBox,,, % "Deleting " this.Name, 0.5
		hwnd := this.hwnd
		Gui %hwnd%: Destroy
		TestClass.DerivedObjects.Delete(hwnd)			; Lexikos: "Use .Delete(hwnd) or .Remove(hwnd, "") and not .Remove(hwnd)."
		TestClass.DerivedObjectsCount -= 1
		if (TestClass.DerivedObjectsCount = 0)			; if no more derived objects
			OnMessage(0x201, this.LBUTTONDOWNFunc, 0)	; remove message handler
	}
}
[solved]

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Decar, doodles333, mikeyww and 220 guests