March 28, 2024, 12:16:15 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


Auto complete on a combo box

Started by Andy, October 01, 2018, 05:23:39 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Andy

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.


Day after day, day after day, we struck nor breath nor motion, as idle as a painted ship upon a painted ocean.

LarryMc

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
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

jalih

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.

fasecero

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


fasecero

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



Andy

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.
:)
Day after day, day after day, we struck nor breath nor motion, as idle as a painted ship upon a painted ocean.