Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Propose new features and changes
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by DRocks » 05 Dec 2019, 00:53

Would it be possible to implement a built-in and documented function that would be able to retrieve a Edit's parent ComboBox hwnd and/or classNN ?
And also the other way around, get the Hwnd of a ComboBox's edit?

I find it hard to get around this still after 1 year and a half with Ahk. Or probably more "confusing than hard" to accomplish.

Code: Select all

;https://www.autohotkey.com/boards/viewtopic.php?p=161032#p161032
hWnd1 := DllCall("user32\GetAncestor", Ptr,hWnd, UInt,1) ;GA_PARENT = 1



A use case scenario is implementation of Auto Completion with ComboBoxes, for example.
Lets say you are in a edit control which belongs to a combobox, but you dont know what combobox is the edit's parent, its hard to act upon that parent child relationship without confusion.


Any comments on this request ?


Thanks in advance
Alex
Last edited by DRocks on 05 Dec 2019, 22:08, edited 2 times in total.
just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Function to get focused Edit's ComboBox hwnd/classNN

Post by just me » 05 Dec 2019, 09:35

Do you talk about AHK GUI ComboBoxes?
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Function to get focused Edit's ComboBox hwnd/classNN

Post by DRocks » 05 Dec 2019, 20:11

just me wrote:
05 Dec 2019, 09:35
Do you talk about AHK GUI ComboBoxes?
Yes mostly, for convenience.
I think it would be useful if the Gui, Add, ComboBox, ... vCb1 gCb1 hwndhCb1 would be aware of its own Edit/ComboBox handle so that you can easily use both if needed.
just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by just me » 06 Dec 2019, 03:53

... would be aware of its own Edit/ComboBox handle so that you can easily use both if needed.
Would you please provide an example of what you'd do using the edit's handle?
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by DRocks » 06 Dec 2019, 16:25

just me wrote:
06 Dec 2019, 03:53
... would be aware of its own Edit/ComboBox handle so that you can easily use both if needed.
Would you please provide an example of what you'd do using the edit's handle?
I'll give a very specific example I am using now but in summary its when you want to use Control, ChooseString but all you know is that your carret is focused in a edit control. How do you know who is it's parent ComboBox hwnd ?

I create a Gui on top of another AHK Gui to make a custom auto-completion feature for a table of data.
When I'm typing in a ComboBox of the table, I want to create that Gui where the carret is typing so that it gives me a bigger font sized Edit control and ListBox while searching for a specific item in a list that automatically gets repopulated while typing. It auto selects the unique match and then inserts it back in the real ComboBox.

When I will want to inject the selected item from the fake ListBox to the real ComboBox, how will I use Control, ChooseString, if I only know the hwnd of its edit control ?

Code: Select all

;--------------------------------------------------------------------------------
CreateCBMatchingGUI(Delimiter:="|") {
;--------------------------------------------------------------------------------
	Global CBMatchingGUI := {}
	
	;...

	; get Parent ComboBox info
	ControlGetFocus, fCtrl ; this will be a Edit control
	ControlGet, hwnd, hwnd,, %fCtrl%
	hCB:=DllCall("user32\GetAncestor", Ptr,hwnd, UInt,1) ;GA_PARENT = 1
	CBMatchingGUI.hParentEdit := hwnd
	CBMatchingGUI.hParentCB := hCB
	
	; set Gui controls with Parent ComboBox info
	WinGetPos, cX, cY, cW, cH, % "ahk_id "CBMatchingGUI.hParentCB
	Gui, Add, Edit, % "+HWNDhEdit x0 y0 w"cW+200 " R1 +Uppercase", %A_GuiControl%
	Gui, Add, ListBox, % "+HWNDhLB xp y+0 wp gCBMatchingLB" " R22", % CbNomList
	;...
}
Later in another function:

Code: Select all

;--------------------------------------------------------------------------------
setCBMatchingGUILBChoice(ByRef CBMatchingGUI) {
;--------------------------------------------------------------------------------
	; get ListBox choice
	GuiControlGet, LBMatchesSelectedChoice,, % CBMatchingGUI.hLB 
	
	; set choice in parent ComboBox
	Control, ChooseString, %LBMatchesSelectedChoice%,,% "ahk_id "CBMatchingGUI.hParentCB
	if (!ErrorLevel)
		Sleep, 60
	CBMatchingGUIDestroy(isCalledDirectly:=true)
}
Spoiler
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by DRocks » 09 Jan 2020, 18:38

To expand on this subject in case any one reads it,
I've been recently discovering Qt for Python which is basically a GUI library.

There is this principle of ComboBox child-parent relationship with the Edit and the List.
For example you can create a ComboBox which is a object by default and then you can call something like:
my_combobox.LineEdit().setText("hey i'm the text in the edit control of combobox")
just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by just me » 10 Jan 2020, 05:45

Code: Select all

my_combobox.LineEdit().setText("hey i'm the text in the edit control of combobox")
Seemingly, the relation is ComboBox->Edit, and not Edit->ComboBox. There are several methods to get the HWND of the Edit after the ComboBox has been created. If you want to know whether an Edit belongs to a ComboBox, you can call GetParent() and check the class.
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by DRocks » 11 Jan 2020, 10:45

just me wrote:
10 Jan 2020, 05:45
Thanks for checking in.
You're right the relationship is ComboBox being the parent of the Edit.

You've made me want to reverse my initial request and go with the order of that parent child relationship just mentionned.

What do you think of this wrapper to create ComboBoxes and storing it as an object including itself and hedit?
Would you recommend encapsulating in a similar way ? :

Code: Select all

#SingleInstance, Force


CB1 := GuiAddCombobox("CB1", "w150")
GuiControl,, % CB1["hwnd"], % "|CB hwnd: "CB1.hwnd "|CB hEdit: " CB1.hEdit "||"
Gui, Show
return


GuiAddCombobox(ByRef hName, options:="", list:="") {
    CB := {}
    Gui, Add, ComboBox, % "HWND"hName " " options, % list
    CB.hwnd := %hName%
    CB.hEdit := GetChildEdit(CB.hwnd)
    return CB ; as object
}
GetChildEdit(ByRef hwnd) {
    WinGet, hEditChild, ControlListHwnd, ahk_id %hwnd%
    WinGetClass, class, % "ahk_id "hEditChild
    if (!class = "Edit")
        msgbox % "Error getting Combobox's child edit hwnd.`nClass was not an Edit"
    return hEditChild
}
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by nnnik » 11 Jan 2020, 12:26

For v2:
The most logical solution would be having an interface for all controls that contain other controls (e. G. Tab controls). Then we can model the ComboBox as a control that contains an Edit control.

For v1 users can probably make their own wrapper functions.
Recommends AHK Studio
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by DRocks » 11 Jan 2020, 17:51

nnnik wrote:
11 Jan 2020, 12:26
Makes sense, thanks for pointing out.
I've been away a while, do you know if v2 is actively on-going ?
just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by just me » 12 Jan 2020, 12:17

If you're sure that HCBB is a handle to a ComboBox control all you need to get the Edit is:

Code: Select all

; -> docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindow
HEDT := DllCall("GetWindow", "Ptr", HCBB, "Int", 5, "UPtr")
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by DRocks » 12 Jan 2020, 21:27

Hello just me, thanks for the more appropriate method you shown.

I think Im slowly getting the hang of translating a simple DllCall like this.
Microsoft explains that the GetWindow function has two parameters :
HWND GetWindow(
HWND hWnd,
UINT uCmd
);

So a DllCall is basically always looking longer than what the microsoft docs show. Hence thats why you see the TYPE beside the actual parameter, am I right?

What Im understanding is that the number 5 in your code is equivalent to GW_CHILD which will be fine with the passed ComboBox hwnd.

Any ways, I think this short DllCall is solving the complexity I was mentionning in the original post.
Thanks, maybe this topic could be marked as solved?
just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by just me » 13 Jan 2020, 05:02

Hello, you're right.

Code: Select all

HWND GetWindow(   <- the type of the returned value is HWND (AHK: Ptr/UPtr)
  HWND hWnd,      <- the type of the first parameter is HWND (AHK: Ptr/UPtr)
  UINT uCmd       <- the type of the second parameter is UINT (AHK: UInt)
);
DllCall requires to pass the type of parameters and return values discretely:

Code: Select all

DllCall("GetWindow"
         , "Ptr", HCBB   <- type, value of the first parameter
         , "UInt", 5     <- type, value of the second parameter (changed to UInt)
         , "UPtr")       <- type of the returned value
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by DRocks » 13 Jan 2020, 18:24

just me wrote:
13 Jan 2020, 05:02
Amazing. Thanks I think this will be handy in the long term. :thumbup:

I have a question:
Why does the

Code: Select all

DllCall
return a different kind of hwnd?
It looks like the hwnd is already a "ahk_id" and the clue that indicates this that it doesn't have the hexadecimal format.
Also you can directly use it in a

Code: Select all

 GuiControlGet control id
CB hEdit: 11612590
CB hwnd: 0x91074c
guest3456
Posts: 3462
Joined: 09 Oct 2013, 10:31

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by guest3456 » 13 Jan 2020, 19:40

DRocks wrote:
13 Jan 2020, 18:24
Why does the

Code: Select all

DllCall
return a different kind of hwnd?
It looks like the hwnd is already a "ahk_id" and the clue that indicates this that it doesn't have the hexadecimal format.
Also you can directly use it in a

Code: Select all

 GuiControlGet control id
CB hEdit: 11612590
CB hwnd: 0x91074c
its the same type of hwnd, just formatted in decimal instead of hex.. see the examples on the SetFormat or Format() help pages to convert back and forth

thebbandit
Posts: 45
Joined: 02 Jul 2019, 11:34

Re: Function to get focused Edit's ComboBox hwnd/classNN or vice versa

Post by thebbandit » 24 Jun 2021, 17:35

I have made use of this Function to great effect in my script, and I wanted to come back to share the modifications I made in order to facilitate the needs of my users.

Instead of a simple single search, it splits apart the search string and finds matches to all parts of the text split at each space.
Then we delay the search until the user stops typing for 400ms in order to allow them to stop typing before it parses the matches again.
This was a huge improvement in the responsiveness of the function when dealing with large lists (over 4k entries)

I added some lines specific for my scripts GUI in the wrapper functions, they need to return if it does not have focus of the correct element on the page.

Code: Select all

; Function: Create Matching ComboBox GUI
;--------------------------------------------------------------------------------
;================================================================================
;#[3.3.2.1 CBMatchingGUI]
#IfWinActive, CBMatchingGUI
;================================================================================
Enter::
NumpadEnter::
Tab::setCBMatchingGUILBChoice(CBMatchingGUI) ; pass GUI object reference

Up::
Down::ControlSend,, % A_ThisHotkey = "Up" ? "{Up}" : "{Down}", % "ahk_id "CBMatchingGUI.hLB

#If WinActive("Add or Edit a Group")
Tab::
	Gui, submit, NoHide
	;...context specific stuff
	KeyWait, Tab
	GuiControlGet, OutputVarE, 2:Focus
	GuiControlGet, varname, 2:Focusv
	If (InStr(varname,"OrFlag") || InStr(varname,"Min") || InStr(varname,"Eval") || InStr(varname,"OrCount") || InStr(varname,"StashTab") || InStr(varname,"Export") || InStr(varname,"groupKey") || InStr(varname,"Click here to Finish and Return to CLF") || InStr(varname,"Remove") || InStr(varname,"Add new"))
		return
	OutputVar := StrReplace(OutputVarE, "Edit", "ComboBox")
	ControlGet, hCBe, hwnd,,%OutputVarE%
	ControlGet, hCB, hwnd,,%OutputVar%
	if (!WinExist("ahk_id "hCBMatchesGui) && hCB && hCBe) {
		CreateCBMatchingGUI(hCB, "Add or Edit a Group")
	}
return

#If WinActive("Edit Crafting Tiers")
Tab::
	Gui, submit, NoHide
	;...context specific stuff
	KeyWait, Tab
	GuiControlGet, OutputVarE, CustomCrafting:Focus
	GuiControlGet, varname, CustomCrafting:Focusv
	If ( InStr(OutputVarE,"SysTabControl") || InStr(OutputVarE,"Button") || !InStr(varname, "CustomCrafting") )
		Return
	OutputVar := StrReplace(OutputVarE, "Edit", "ComboBox")
	ControlGet, hCBe, hwnd,,%OutputVarE%
	ControlGet, hCB, hwnd,,%OutputVar%
	if (!WinExist("ahk_id "hCBMatchesGui) && hCB && hCBe) {
		CreateCBMatchingGUI(hCB, "Edit Crafting Tiers")
	}
return

CreateCBMatchingGUI(hCB, parentWindowTitle) {
;--------------------------------------------------------------------------------
	Global CBMatchingGUI := {}
	Gui CBMatchingGUI:New, -Caption -SysMenu -Resize +ToolWindow +AlwaysOnTop
	Gui, +HWNDhCBMatchesGui +Delimiter`n
	Gui, Margin, 0, 0
	Gui, Font, s14 q5
	
	; get Parent ComboBox info
	WinGetPos, cX, cY, cW, cH, % "ahk_id " hCB
	ControlGet, CBList, List,,, % "ahk_id " hCB
	; MsgBox % ErrorLevel
	ControlGet, CBChoice, Choice,,, % "ahk_id " hCB
	; MsgBox % CBList ? "True" : "False"
	; set Gui controls with Parent ComboBox info
	Gui, Add, Edit, % "+HWNDhEdit x0 y0 w"cW+400 " R1"
	GuiControl,, %hEdit%, %CBChoice%
	Gui, Add, ListBox, % "+HWNDhLB xp y+0 wp" " R20", % CBList
	GuiControl, ChooseString, %hLB%, %CBChoice%
	
	CBMatchingGUI.hwnd := hCBMatchesGui
	CBMatchingGUI.hEdit := hEdit
	CBMatchingGUI.hLB := hLB
	CBMatchingGUI.hParentCB := hCB
	CBMatchingGUI.parentCBList := CBList
	CBMatchingGUI.parentWindowTitle := parentWindowTitle
	
	gFunction := Func("CBMatching").Bind(CBMatchingGUI)
	tFunction := Func("FuncTimer").Bind(gFunction,400)
	GuiControl, +g, %hEdit%, %tFunction%
	
	Gui, Show, % "x"cX-50 " y"cY-5 " ", % "CBMatchingGUI"
	ControlFocus,, % "ahk_id "CBMatchingGUI.hEdit
	SetTimer, DestroyCBMatchingGUI, 80
}
FuncTimer(funcobj,duration){
	SetTimer % funcobj,% "-" duration
}
;--------------------------------------------------------------------------------
CBMatching(ByRef CBMatchingGUI) { ; ByRef object generated at the GUI creation
;--------------------------------------------------------------------------------
	GuiControlGet, userInput,, % CBMatchingGUI.hEdit
	userInputArr := StrSplit(RTrim(userInput), " ")
	; choicesList := CBMatchingGUI.parentCBList
	MatchCount := MatchList := MisMatchList := 0
	matchArr := {}
	;--Find in list
	for k, v in userInputArr
	{
		If (v = "")
			Continue
		If (InStr(CBMatchingGUI.parentCBList, v))
			MatchList := True
		else
			MisMatchList := True
	}
	if (MatchList && !MisMatchList) {
		; Loop, Parse, choicesList, "`n"
		For index, choice in StrSplit(CBMatchingGUI.parentCBList,"`n"," `r")
		{
			if choice = ""
				continue
			MatchString := MisMatchString := 0
			posArr := {}
			for k, v in userInputArr
			{
				If (FoundPos := InStr(choice, v))
				{
					MatchString := True
					posArr.Push(FoundPos)
				}
				else
					MisMatchString := True
			}
			If (MatchString && !MisMatchString)
			{
				For k, v in posArr
				{
					If (v = 1 && A_Index = 1)
						atStart := True
				}
				If !IndexOf(choice,matchArr)
				{
					If (atStart)
						MatchesAtStart .= "`n"choice
					else
						MatchesAnywhere .= "`n"choice
					MatchCount++
					matchArr.Push(choice)
				}
			}
		}
		Matches := MatchesAtStart . MatchesAnywhere ; Ordered Match list
		GuiControl,, % CBMatchingGUI.hLB, %Matches%
		if (MatchCount = 1) {
			UniqueMatch := Matches
			GuiControl, ChooseString, % CBMatchingGUI.hLB, %UniqueMatch%
		} 
		else
			GuiControl, Choose, % CBMatchingGUI.hLB, 1
	} 
	else
		GuiControl,, % CBMatchingGUI.hLB, `n<! No Match !>
}

;--------------------------------------------------------------------------------
DestroyCBMatchingGUI() {
;--------------------------------------------------------------------------------
	Global CBMatchingGUI ; global object created with the CBMatchingGUI
	
	if (!WinActive("Ahk_id " CBMatchingGUI.hwnd) and WinExist("ahk_id " CBMatchingGUI.hwnd)) {
		Gui, % CBMatchingGUI.hwnd ":Destroy"
		SetTimer, DestroyCBMatchingGUI, Delete
	}
}

;--------------------------------------------------------------------------------
setCBMatchingGUILBChoice(CBMatchingGUI) {
;--------------------------------------------------------------------------------
	; get ListBox choice
	GuiControlGet, LBMatchesSelectedChoice,, % CBMatchingGUI.hLB 
	; set choice in parent ComboBox
	Control, ChooseString, %LBMatchesSelectedChoice%,,% "ahk_id "CBMatchingGUI.hParentCB
	; set focus to Parent ComboBox, this will destroy matching GUI
	ControlFocus,, % "ahk_id "CBMatchingGUI.hParentCB
}
One of the more useful functions when working with long lists, it definitely improved my script by a TON. Thanks, and cheers for sharing the lib
Post Reply

Return to “Wish List”