[Collab] Typing suggestions for ComboBox lists example provided

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

[Collab] Typing suggestions for ComboBox lists example provided

29 Jan 2020, 09:06

Here's an example script of match suggestions while typing in a AHK GUI ComboBox.

2020-02-01: The list filter currently uses RegExMatch() for a few kind of possible matches in this importance order:
1. case sensitive matches Uppercase Letters in list item ("LF" input would match "Location Fees")
2. case sensitive matches at start of list item
3. case insensitive matches at start of list item
4. case sensitive matches anywhere in list item
5. case insensitive matches anywhere in list item

Please try it out and see if we can make this become more advanced similar to the Google search suggestions, or IDE style suggestions.
Then if we get a great result we could wrap it up as a easy to use class or something to share in the Scripts and functions section of the forums.

Code: Select all

;2020-02-01
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#SingleInstance, Force ; Prevent double execution of this script
SetBatchLines, -1

Gui, MainUI:New,, % "Input Suggestions Test"
Gui, Font, s20
Gui, Add, Text,, Type something to get suggestions:

Gui, % "+Delimiter" . "`n"
CBBList :="`n" . "4200 Revenu (Général)"
		. "`n" . "4201 Vente"
		. "`n" . "4202 Service"
		. "`n" . "4203 Consultation"
		. "`n" . "4204 Location"
		. "`n" . "4205 Pourboire"
		. "`n" . "4210 Revenu d'Intérêt"
		. "`n" . "5700 Fourniture"
		. "`n" . "5701 Location Outils et équipement"
		. "`n" . "5721 Location Véhicule"
		. "`n" . "5722 Entretien Véhicule"
		. "`n" . "5723 Essence Véhicule"
		. "`n" . "5724 Frais de Déplacement"
		. "`n" . "5725 Télécommunication"
		. "`n" . "5726 Poste et Papeterie (matériel bureau)"
		. "`n" . "5784 Voyage Professionnel"
		. "`n" . "9999 Location Fees"
Gui, Add, ComboBox, vCBB y+5 w550 R7, % CBBList
CBBLabelFunction := Func("CBBLabel").Bind("`n")
GuiControl, +g, CBB, %CBBLabelFunction%

Gui, Add, ListBox, vsuggestionsListBox y+0 wp R7 +Hidden
Gui, Show
return


;--------------------------------------------------------------------------------
CBBLabel(ByRef listDelimiter) {
;--------------------------------------------------------------------------------
	GuiControlGet, userInput,  MainUI:, %A_GuiControl%
	hCB := getEditParentComboBoxHwnd()
	if ((!Trim(userInput)) or (!hCB)) {
		GuiControl, MainUI:Hide, suggestionsListBox
		Return
	}
	
	ControlGet, CBList, List,,, % "ahk_id "hCB
	suggestions := {}
	suggestions := GetInputSuggestions(userInput, CBList, listDelimiter, 7)
	if (suggestions["count"] >= 1) {
		GuiControl, MainUI:Show  , suggestionsListBox
		GuiControl, MainUI:  	 , suggestionsListBox, % listDelimiter . suggestions["list"]
		GuiControl, MainUI:Choose, suggestionsListBox, 1
	} else {
		GuiControl, MainUI:Choose, suggestionsListBox, 0
		GuiControl, MainUI:Hide, suggestionsListBox
	}
}


;--------------------------------------------------------------------------------
GetInputSuggestions(ByRef userInput, ByRef list, listDelimiter:="`n", maxReturnedSuggestions:=7) {
;--------------------------------------------------------------------------------
	/* 	Inputs: Currently typed text input and list of possible matches.
		* By default, list is delimited by "`n".
		Returns: An object containing the number of matches and the suggestions list.
		* By default, list is delimited by "`n".
		* There will be a maximum of maxReturnedSuggestions in that list.
	*/
	matches := {}
	for each, list_item in StrSplit(list, listDelimiter) {
		importance_factor := 0
		capsOnly := RegExReplace(list_item, "[^A-Z]")
		if ((StrLen(capsOnly) > 1) and RegExMatch(capsOnly, "^" . userInput . "$")) {
			matches_count++
			importance_factor := 10000
			matches.InsertAt(0, list_item) 
		} 
		else {
			word_matches_count := 0
			for index, word in StrSplit(list_item, A_Space) {
				if (RegExMatch(word, "^"userinput)) {
					word_matches_count++
					importance_factor := importance_factor + 9000 - (index * maxReturnedSuggestions)
				}
				else if (RegExMatch(word, "i)^" . userinput)) {
					word_matches_count++
					importance_factor := importance_factor + 8000 - (index * maxReturnedSuggestions)
				}
				else if (RegExMatch(word, ".*" . userinput . ".*")) {
					word_matches_count++
					importance_factor := importance_factor + 1000 - (index * maxReturnedSuggestions)
				}
				else if (RegExMatch(word, "i).*" . userinput . ".*")) {
					word_matches_count++
					importance_factor := importance_factor + 500 - (index * maxReturnedSuggestions)
				}
				else {
					importance_factor := importance_factor - index
				}
			}
			if (word_matches_count > 0) {
				matches_count++
				sorted_importance_list .= importance_factor . ","
				Sort sorted_importance_list, N R D,  ; Sort numerically, use comma as delimiter.
				for i, position in StrSplit(sorted_importance_list, ",") {
					if (position = importance_factor) {
						matches.InsertAt(i, list_item)
						Break
					}
				}
			}
		}
	}
	
	for each, item in matches {
		matches_list .= item . listDelimiter
	}
	return { "list": matches_list
		   , "count": matches_count }
}


;--------------------------------------------------------------------------------
getControlHwnd(ctrl) {
;--------------------------------------------------------------------------------
	ControlGet, hwnd, HWND,, %ctrl%
	return hwnd
}


;--------------------------------------------------------------------------------
getEditParentComboBoxHwnd() {
;--------------------------------------------------------------------------------
	static GA_PARENT = 1
	ControlGetFocus, focusedCtrl
	if (!RegExMatch(focusedCtrl, "Edit\d+")) {
		MsgBox % "Focused Control is not of class Edit - can't associate to a ComboBox"
		return
	}
	ControlGet, hEdit, HWND,, %focusedCtrl%
	return DllCall("user32\GetAncestor"
					, Ptr,hEdit
					, UInt,GA_PARENT)
}


; Inject Selected ListBox Suggestion to the ComboBox in which we type.
~Tab::
~Enter::
~NumpadEnter::
GuiControlGet, LBChoice, MainUI:, suggestionsListBox
GuiControl, MainUI:ChooseString, CBB, %LBChoice%
GuiControl, MainUI:Hide, suggestionsListBox
return


MainUIGuiEscape:
MainUIGuiClose:
ExitApp
In any case, feel free to use it if you're not interested in sharing your ideas
Last edited by DRocks on 02 Feb 2020, 08:11, edited 6 times in total.
User avatar
TheDewd
Posts: 1503
Joined: 19 Dec 2013, 11:16
Location: USA

Re: [Collab] Typing suggestions for ComboBox lists example provided

29 Jan 2020, 11:24

Maybe the ListBox should be hidden when the user removes all text from the ComboBox using BackSpace or Delete?
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Collab] Typing suggestions for ComboBox lists example provided

29 Jan 2020, 13:08

I think ListFiltering() should take at least two parameters (currently zero)
The first parameter is what is currently entered, the 2nd parameter is an array of objects or strings of possible matches. A possible 3rd parameter of options like how many results should be returned.

This function is called repeatedly or on a timer whenever needed to make the latest list of possible matches. Get all this GUI juggling out of the function.
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: [Collab] Typing suggestions for ComboBox lists example provided

30 Jan 2020, 09:53

Thanks for your replies, I've been doing what you both suggested. (original post is updated)

@TheDewd Added hidding Suggestion ListBox when userInput becomes empty

@Chunjee Separated Gui stuff from the main filtering function now called GetInputSuggestion()
- Added parameters like you said.
- Removed any global reference needs.
- Added option to change default lsit delimiter.
- Function now returns an array with matches count and matches list.


I need help to understand how to deal with placing matches in multiple words of a list item and detect when matches are doubled because of the nature of the words loop.
For example, if you type "v" I'd like to have the matches ordered by some priorities like:
1. "v" is the first letter in one or more of the list item's words (more matches would make this item more important)
2. do not add the item to matches if already in the match list
3. stop if maxNumber of suggestions is reached
etc.

Any hints ?
User avatar
TheDewd
Posts: 1503
Joined: 19 Dec 2013, 11:16
Location: USA

Re: [Collab] Typing suggestions for ComboBox lists example provided

31 Jan 2020, 10:34

If you type "5784" you see suggestion for "5784 Voyage Professionnel".

If you type "57888888888" the same suggestion remains, though it is not selected.

If you paste "57888888888" into the empty Edit control, it shows 0 suggestions.

Which is correct? Should the ListBox be hidden when you type the first "8" after "5784", because the suggestion no longer matches the entered text? Or, should pasting "57888888888" make the suggestion for "5784 Voyage Professionnel" appear?
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: [Collab] Typing suggestions for ComboBox lists example provided

31 Jan 2020, 12:48

Good question, I think I'd prefer that it disappears in both cases but this case is not handled yet good find.
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: [Collab] Typing suggestions for ComboBox lists example provided

01 Feb 2020, 12:10

Updated original post with @TheDewd 's suggestion.

I also made the sorting of results a bit more useful in my opinion.
The logic could be much better, but for now it's best I can get.

There's a problem when you get a match without considering the numbers in front, the numbers will get reversed.
Like for example, when it matches "V" which is present in 5721 Location Véhicule, 5722 Entretien Véhicule, 5723 Essences Véhicule.
The final sorted list will be reversed in relation to the numbers meaning you will see 5723... followed by 5722... then 5721...

Feel free to share your ideas again :)
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Collab] Typing suggestions for ComboBox lists example provided

02 Feb 2020, 07:18

Sorry I screwed up after thinking about it the array/list should be the first parameter.

I don't see what these byrefs or global matches := {} is accomplishing. I removed them and it ran just the same.
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: [Collab] Typing suggestions for ComboBox lists example provided

02 Feb 2020, 08:09

Chunjee wrote:
02 Feb 2020, 07:18
I don't see what these byrefs or global matches := {} is accomplishing. I removed them and it ran just the same.
I read once that ByRefs use the same reference location in memory so I like using it to make it clear I'm using something linked with the outer scope. Secondly, I read that it's good when passing long string so for the list param it would be optimal. The others ones could go away if needed to make code clearer to others.

The global matches is useless to the script you're right, I forgot to remove the global word because I was using "DebugVars.ahk" by lexikos to check the object content in real time without the GUI. It needed to be a global avr to show up in that other script.

If you look up at the filtering logic you'll see its very weak and it needs love by external brains lol :D
:thumbup:
ecostaoli
Posts: 1
Joined: 26 Apr 2022, 12:27

Re: [Collab] Typing suggestions for ComboBox lists example provided

26 Apr 2022, 14:47

Tout d'abord, merci bien! :D :D
Only it's needed to escape some characters: $, +, |... with an backslash "\". That only doesn't work for 0's, one needs to use \Q<any quantity of zeros desired>\E

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: DIYCB and 40 guests