I was wondering if it is possible to do some sort of auto complete in a combo box as the user is typing.
If we take a combo box and add some strings to it say:
Athens
London
New Deli
New York
Paris
Then the user types 'n" New Deli should be highlighted.
I've got that far, but I have to click on the drop down box to see the selection.
And when I do so, the combo edit box is populated with New Deli.
The cursor is then placed at the end of the text.
Unless I click the drop down box, I can't get the selection position / number.
What I'd like to do is to type say "ne" and have New Deli selection placed in the edit box (2)
and if I carry on typing, and get to "new y" to place New York in the edit box.
Can this be done? or auto complete what I'm typing in the combo edit box BUT still allow me to continue typing i.e. not just to the end of the text?
This is what I've got so far:
$include "windowssdk.inc"
int pos = 0
int fm = 0
window win,msb
OPENWINDOW win,0,0,600,400,@CAPTION|@SYSMENU|@MINBOX,0,"",&handler_win
CONTROL win,@COMBOBOX,"",20,60,200,500,@CTCOMBODROPDOWN|@VSCROLL|WM_NOTIFY|@TABSTOP,1
CONTROL win,@EDIT,"",20,100,400,25,@CTEDITLEFT,2
addstring win,1,"Athens"
addstring win,1,"London"
addstring win,1,"New Deli"
addstring win,1,"New York"
addstring win,1,"Paris"
waituntil win=0
end
SUB handler_win(),int
SELECT @MESSAGE
CASE @IDCONTROL
SELECT @CONTROLID
case 1
IF @NOTIFYCODE=CBN_EDITCHANGE
sendmessage win.hwnd,CB_FINDSTRING,1,1
fm=sendmessage win.hwnd,CB_GETCURSEL,0,0,1
'sendmessage win.hwnd,CB_SHOWDROPDOWN,TRUE,0,1
pos = GETSELECTED(win,1)
setcaption win,str$(fm)
'setfocus win,1
'sendmessage win.hwnd,CB_SHOWDROPDOWN,FALSE,0,1
endif
endselect
CASE @IDCREATE
centerwindow win
case @IDCLOSEWINDOW
closewindow win
end
endselect
return 0
endsub
Thanks,
Andy.
Doing what you want, I believe, is both counter intuitive, and will be difficult.
The reason I say it is counter intuitive is because we don't normally display text and type new characters on top in the middle of a text string.
If you activate and look at AUTOCOMPLETE feature of IWBasic's IDE you'll see that when you type a letter all the possibilities are listed in a box underneath and as you add letters the non possibilities are removed and at anytime you can select the keyword you actually want.
To do sort of what you want with a combobox I think you would have to subclass both the edit control portion and the listbox control portions of the combobox control to manipulate what is listed in the edit control portion and to allow/disallow what is displayed in the listview portion.
If it was me, I would do it with a separate edit and listview with the listwiew being hidden most of the time.
I would have a separate array holding all my entries.
the edit control would subclassed to restrict letter entries.
initially it would be restricted to the 1st letter of each of the entries which would be loaded into the listview.
then the 2nd letter would be limited to a combination that matched the 1st 2 letters in the listview and so on; eliminating non matches from the listview
you get the drift.
when you have only one entry left in the listview then you hide the listview and that's your entry
Quote from: LarryMc on October 01, 2018, 08:06:55 AM
initially it would be restricted to the 1st letter of each of the entries which would be loaded into the listview.
then the 2nd letter would be limited to a combination that matched the 1st 2 letters in the listview and so on; eliminating non matches from the listview
you get the drift.
when you have only one entry left in the listview then you hide the listview and that's your entry
You could use BK-Tree for really fast approximate string matching.
I've played a little and this is my test piece. I should mention that winapi has a function called SHAutoComplete but it serves only for URLs and file system paths. To customize this behavior to your oown set of strings you'll need to implement IAutoComplete + IEnumString COM objects.
$INCLUDE "windowssdk.inc"
CONST NROITEMS = 5 ' combobox items number
string comboSource[NROITEMS] ' combobox string items
CONST COMBO_1 = 1
WINDOW w1
OPENWINDOW w1,0,0,600,400,@MINBOX|@MAXBOX|@SIZE,NULL,"Simple Window",&w1_handler
CONTROL w1,@COMBOBOX,"ComboBox1",77,58,153,200,@CTCOMBODROPDOWN,COMBO_1
InitComboBoxSource()
FilterComboBox(w1, COMBO_1)
' main loop
WAITUNTIL w1 = 0
END
' window procedure
SUB w1_handler
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW w1
CASE @IDCLOSEWINDOW
CLOSEWINDOW w1
CASE @IDCONTROL
SELECT @CONTROLID
CASE COMBO_1
SELECT @NOTIFYCODE
CASE @CBNEDITCHANGE
' combobox edit text has changed
string filter = GetComboCurrentText(w1, COMBO_1)
FilterComboBox(w1, COMBO_1, filter)
ENDSELECT
ENDSELECT
ENDSELECT
ENDSUB
SUB InitComboBoxSource()
comboSource[0] = "Athens"
comboSource[1] = "London"
comboSource[2] = "New Deli"
comboSource[3] = "New York"
comboSource[4] = "Paris"
ENDSUB
SUB ShowDropDownCombo(WINDOW w, INT comboID, INT show)
_SendMessage(GetDlgItem(w.hwnd, comboID), CB_SHOWDROPDOWN, show, 0)
ENDSUB
SUB GetComboCurrentText(WINDOW w, INT comboID), string
STRING buffer = ""
INT combohwnd = GetDlgItem(w.hwnd, comboID)
INT iLen = GetWindowTextLength(combohwnd) + 1
LPSTR szText = LocalAlloc(LPTR, iLen)
iLen = GetWindowText(combohwnd, szText, iLen)
buffer = *<string>szText
LocalFree(szText)
RETURN buffer
ENDSUB
SUB FilterComboBox(WINDOW w, INT comboID, OPT string filter = "")
INT j, count, hwndedit
' clear the combobox
_SendMessage(GetDlgItem(w.hwnd, comboID), CB_RESETCONTENT, 0, 0)
SETCONTROLTEXT(w, comboID, filter)
' fill the dropdown items
FOR j = 0 TO NROITEMS - 1
IF filter = "" THEN
ADDSTRING w1, COMBO_1, comboSource[j]
ELSE
IF INSTR(LCASE$(comboSource[j]), LCASE$(filter)) THEN ADDSTRING w1, COMBO_1, comboSource[j]
ENDIF
NEXT j
' when the dropdown need to appear
count = GETSTRINGCOUNT (w, COMBO_1)
IF count = 0 OR count = NROITEMS THEN
ShowDropDownCombo(w, comboID, FALSE)
ELSE
ShowDropDownCombo(w, comboID, TRUE)
ENDIF
' set the caret position at the end?
hwndedit = GetWindow(GetDlgItem(w.hwnd, comboID), GW_CHILD)
_SendMessage(hwndedit, EM_SETSEL , LEN(filter)+1, LEN(filter)+1)
' ShowDropDownCombo hides the cursor
_SendMessage(GetDlgItem(w.hwnd, comboID), WM_SETCURSOR, 0, 0)
ENDSUB
Hey I was about to implement IAutoComplete + IEnumString to learn this stuff but after reading your question for the second time I realize that you don't want to deploy the combobox. So if you only need the autocomplete feature I think this is exactly what you want
$INCLUDE "windowssdk.inc"
INT lenghtMem = 0
BOOL InChangeEvent = FALSE
WINDOW win
OPENWINDOW win,0,0,600,400,@CAPTION|@SYSMENU|@MINBOX,0,"",&handler_win
CONTROL win,@COMBOBOX,"",20,60,200,500,@CTCOMBODROPDOWN|@VSCROLL|@TABSTOP,1
CONTROL win,@EDIT,"",20,100,400,25,@CTEDITLEFT,2
ADDSTRING win,1,"Athens"
ADDSTRING win,1,"London"
ADDSTRING win,1,"New Deli"
ADDSTRING win,1,"New York"
ADDSTRING win,1,"Paris"
WAITUNTIL ISWINDOWCLOSED(win)
END
SUB handler_win(),int
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW win
CASE @IDCLOSEWINDOW
CLOSEWINDOW win
CASE @IDCONTROL
SELECT @CONTROLID
CASE 1
SELECT @NOTIFYCODE
CASE @CBNEDITCHANGE
CBTextChanged(GetDlgItem(win.hwnd, 1), GETCONTROLTEXT(win, 1))
ENDSELECT
ENDSELECT
ENDSELECT
RETURN 0
ENDSUB
SUB CBAutocomplete(INT hwndCombo, string strSearch, INT OldSelStart)
INT smResult = _SendMessage(hwndCombo, CB_FINDSTRING, 0, &strSearch)
IF smResult <> CB_ERR THEN
_SendMessage(hwndCombo, CB_SETCURSEL, smResult, 0)
_SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(OldSelStart, -1))
ENDIF
ENDSUB
SUB CBTextChanged(INT hwndCombo, string strSearch)
IF lenghtMem >= LEN(strSearch) THEN
lenghtMem = LEN(strSearch)
RETURN
ENDIF
lenghtMem = LEN(strSearch)
IF InChangeEvent = FALSE THEN
InChangeEvent = TRUE
INT OldSelStart = LEN(strSearch)
CBAutocomplete(hwndCombo, LEFT$(strSearch, OldSelStart), OldSelStart)
InChangeEvent = FALSE
ENDIF
ENDSUB
Fasecero,
Thanks, sorry not been back to any one on this just been very busy.
I will have a look today at the code.
Thanks,
Andy.
:)