Page 1 of 2

Autocomplete for Comboboxgadget - Simple version

Posted: Sun Nov 08, 2009 3:24 am
by mesozorn
I have seen other autocomplete routines posted which are very good indeed. This is mine which is very short and very simple:

Code: Select all

Structure comboboxinfo
  cbSize.l
  rcItem.RECT
  rcButton.RECT
  stateButton.l
  hwndCombo.l
  hwndEdit.l
  hwndList.l
EndStructure

cbinfo.comboboxinfo


OpenWindow(0, 0, 0, 270, 140, "ComboBoxGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ComboBoxGadget(0, 10, 10, 250, 21, #PB_ComboBox_Editable)
  AddGadgetItem(0, -1, "Adams")
  AddGadgetItem(0, -1, "Franklin")
  AddGadgetItem(0, -1, "Jefferson")
  AddGadgetItem(0, -1, "Walters")
  AddGadgetItem(0, -1, "Washington")
 SetActiveGadget(0)


Repeat
  W=WaitWindowEvent()
      
      
  If W=#WM_KEYDOWN And  GetActiveGadget()=0
    acg=GetActiveGadget()
    cbinfo\cbsize=SizeOf(comboboxinfo)
    GetComboBoxInfo_(GadgetID(acg),@cbinfo)
    tb=cbinfo.comboboxinfo\hwndedit
    SendMessage_(tb, #EM_GETSEL, @spos.l, @epos.l) 
      
    matchesfound=0  
      For x=0 To CountGadgetItems(acg)-1
        If LCase(Left(GetGadgetText(acg),spos)+LCase(Chr(EventwParam())))=LCase(Left(GetGadgetItemText(acg,x),spos+1)) And epos=Len(GetGadgetText(acg))
          matchesfound+1:match=x
          If matchesfound>1:Break:EndIf
        EndIf
      Next x
      
      If matchesfound=1  
          ks=GetKeyState_(#VK_SHIFT)
          If ks<2:addchar$=LCase(Chr(EventwParam())):Else:addchar$=UCase(Chr(EventwParam())):EndIf
          SetGadgetText(acg,Left(GetGadgetText(acg),spos)+addchar$+Mid(GetGadgetItemText(acg,match),spos+2))
          SendMessage_(tb, #EM_SETSEL, spos+1, epos+999) 
          PeekMessage_(@msg.MSG, GadgetID(acg), #WM_KEYFIRST, #WM_KEYLAST, #PM_REMOVE)
      EndIf
      
  EndIf
          
    
Until W = #PB_Event_CloseWindow
It is of course very easy to enhance this so as to be able to add autocomplete functionality to any comboboxgadget created on the fly, without having to go back and edit this source to include each new gadget. I have not added these extra lines to the example above only because it is a fairly obvious enhancement.

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Tue Nov 10, 2009 8:38 pm
by akj
@mesozorn:

There is a bug in your routine in that hWnd is never assigned a value, but is used in the line

Code: Select all

PeekMessage_(@msg, hWnd, #WM_KEYFIRST, #WM_KEYLAST, #PM_REMOVE)

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Tue Nov 10, 2009 8:56 pm
by mesozorn
akj wrote:@mesozorn:

There is a bug in your routine in that hWnd is never assigned a value, but is used in the line

Code: Select all

PeekMessage_(@msg, hWnd, #WM_KEYFIRST, #WM_KEYLAST, #PM_REMOVE)
Thanks for noticing. It still seems to work correctly even if hwnd is left as 0 regardless of anything else, but just to be safe I have now edited it to be:

Code: Select all

PeekMessage_(@msg.MSG, GadgetID(acg), #WM_KEYFIRST, #WM_KEYLAST, #PM_REMOVE)

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Fri Nov 13, 2009 7:23 pm
by Amnesty
Its amazing simple, thank you for this.

How can I use this with numbers, I am thinking about customer IDs. (for example 11208736)
When I am using only numbers in Comboboxgadget, autocomplete is not working.

Sorry, it works with numbers but not with NumPad.

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Fri Nov 13, 2009 8:13 pm
by mesozorn
This modified version works for NumPad, but there are still a bunch of other non-standard characters which will trip it up as well. I will post a further modified version later which will handle all types of keys. The original version is mainly for regular letters and numbers.

Code: Select all

Structure comboboxinfo
  cbSize.l
  rcItem.RECT
  rcButton.RECT
  stateButton.l
  hwndCombo.l
  hwndEdit.l
  hwndList.l
EndStructure

cbinfo.comboboxinfo


OpenWindow(0, 0, 0, 270, 140, "ComboBoxGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ComboBoxGadget(0, 10, 10, 250, 21, #PB_ComboBox_Editable)
  AddGadgetItem(0, -1, "12345")
  AddGadgetItem(0, -1, "23468")
  AddGadgetItem(0, -1, "48347")
  AddGadgetItem(0, -1, "58237")
  AddGadgetItem(0, -1, "58772")
SetActiveGadget(0)


Repeat
  W=WaitWindowEvent()
     
     
  If W=#WM_KEYDOWN And  GetActiveGadget()=0
    acg=GetActiveGadget()
    cbinfo\cbsize=SizeOf(comboboxinfo)
    GetComboBoxInfo_(GadgetID(acg),@cbinfo)
    tb=cbinfo.comboboxinfo\hwndedit
    SendMessage_(tb, #EM_GETSEL, @spos.l, @epos.l)
    
        
    matchesfound=0 
      For x=0 To CountGadgetItems(acg)-1
        If (LCase(Left(GetGadgetText(acg),spos)+LCase(Chr(EventwParam())))=LCase(Left(GetGadgetItemText(acg,x),spos+1)) And epos=Len(GetGadgetText(acg))) Or (EventwParam()>=96 And EventwParam()<=105 And LCase(Left(GetGadgetText(acg),spos)+LCase(Chr(EventwParam()-48)))=LCase(Left(GetGadgetItemText(acg,x),spos+1)) And epos=Len(GetGadgetText(acg)))

          matchesfound+1:match=x
          If matchesfound>1:Break:EndIf
        EndIf
      Next x
     
      If matchesfound=1 
          If EventwParam()>=96 And EventwParam()<=105:ep=EventwParam()-48:Else:ep=EventwParam():EndIf
          ks=GetKeyState_(#VK_SHIFT)
          If ks<2:addchar$=LCase(Chr(ep)):Else:addchar$=UCase(Chr(ep)):EndIf
          SetGadgetText(acg,Left(GetGadgetText(acg),spos)+addchar$+Mid(GetGadgetItemText(acg,match),spos+2))
          SendMessage_(tb, #EM_SETSEL, spos+1, epos+999)
          PeekMessage_(@msg.MSG, GadgetID(acg), #WM_KEYFIRST, #WM_KEYLAST, #PM_REMOVE)
      EndIf
     
  EndIf

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Fri Nov 13, 2009 9:32 pm
by mesozorn
Okay, the new version below works for all keys and characters I believe. I switched to using the subclassing approach which makes it even more APISH than before, but I was already using plenty of API calls in the original version, so not that much difference I suppose. Just note two things:

1) It is the textbox portion of the ComboBoxGadget which gets subclassed, and not the entire ComboBoxGadget itself. You can use a simple procedure or macro to do this in one clean step, but I've left the long version in for illustration.

2) This is my first attempt ever at subclassing, so it may be buggy.

Code: Select all

Structure comboboxinfo  
  cbSize.l
  rcItem.RECT
  rcButton.RECT
  stateButton.l
  hwndCombo.l
  hwndEdit.l
  hwndList.l
EndStructure
Global cbinfo.comboboxinfo
Global oldcomboProc


Procedure.i ComboAutoComplete(hWnd, uMsg, wParam, lParam)
  Protected result
  Select uMsg
    Case #WM_CHAR
    acg=GetActiveGadget() ;GetDlgCtrlID_(hwnd)
    SendMessage_(hwnd, #EM_GETSEL, @spos.l, @epos.l)

    matchesfound=0 
      For x=0 To CountGadgetItems(acg)-1
        If LCase(Left(GetGadgetText(acg),spos)+LCase(Chr(wParam)))=LCase(Left(GetGadgetItemText(acg,x),spos+1)) And epos=Len(GetGadgetText(acg))
          matchesfound+1:match=x
          If matchesfound>1:Break:EndIf
        EndIf
      Next x
     
      If matchesfound=1 
          ks=GetKeyState_(#VK_SHIFT)
          If ks<2:addchar$=LCase(Chr(wparam)):Else:addchar$=UCase(Chr(wparam)):EndIf
          SetGadgetText(acg,Left(GetGadgetText(acg),spos)+addchar$+Mid(GetGadgetItemText(acg,match),spos+2))
          SendMessage_(hwnd, #EM_SETSEL, spos+1, epos+999)
          result=0
      Else
        result = CallWindowProc_(oldcomboproc, hWnd, uMsg, wParam, lParam)
      EndIf
      
    Default
      result = CallWindowProc_(oldcomboproc, hWnd, uMsg, wParam, lParam)
  EndSelect
  ProcedureReturn result
EndProcedure


OpenWindow(0, 0, 0, 270, 140, "ComboBoxGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ComboBoxGadget(0, 10, 10, 250, 21, #PB_ComboBox_Editable)
  cbinfo\cbsize=SizeOf(comboboxinfo)
  GetComboBoxInfo_(GadgetID(0),@cbinfo)
  tb=cbinfo.comboboxinfo\hwndedit
  oldcomboproc = SetWindowLong_(tb, #GWL_WNDPROC, @ComboAutoComplete())
  AddGadgetItem(0, -1, "12345")
  AddGadgetItem(0, -1, "23/468")
  AddGadgetItem(0, -1, "483~47")
  AddGadgetItem(0, -1, "58237")
  AddGadgetItem(0, -1, "58.772")
SetActiveGadget(0)


Repeat
  W=WaitWindowEvent()
Until W = #PB_Event_CloseWindow

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Mon Apr 12, 2010 5:18 pm
by ThoPie
Unfortunately, this great ComboBox-Autocompletion works not with PureBasic 4.5x. Please can anybody adapt this for the usage with version 4.5.
Thanks a lot.

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Mon Apr 12, 2010 5:25 pm
by srod
GetComboBoxInfo_() is not reliable for the new combos. I have replaced this and the example seems to work ok.

Code: Select all

Global oldcomboProc


Procedure.i ComboAutoComplete(hWnd, uMsg, wParam, lParam)
  Protected result
  Select uMsg
    Case #WM_CHAR
    acg=GetActiveGadget() ;GetDlgCtrlID_(hwnd)
    SendMessage_(hwnd, #EM_GETSEL, @spos.l, @epos.l)

    matchesfound=0 
      For x=0 To CountGadgetItems(acg)-1
        If LCase(Left(GetGadgetText(acg),spos)+LCase(Chr(wParam)))=LCase(Left(GetGadgetItemText(acg,x),spos+1)) And epos=Len(GetGadgetText(acg))
          matchesfound+1:match=x
          If matchesfound>1:Break:EndIf
        EndIf
      Next x
     
      If matchesfound=1 
          ks=GetKeyState_(#VK_SHIFT)
          If ks<2:addchar$=LCase(Chr(wparam)):Else:addchar$=UCase(Chr(wparam)):EndIf
          SetGadgetText(acg,Left(GetGadgetText(acg),spos)+addchar$+Mid(GetGadgetItemText(acg,match),spos+2))
          SendMessage_(hwnd, #EM_SETSEL, spos+1, epos+999)
          result=0
      Else
        result = CallWindowProc_(oldcomboproc, hWnd, uMsg, wParam, lParam)
      EndIf
      
    Default
      result = CallWindowProc_(oldcomboproc, hWnd, uMsg, wParam, lParam)
  EndSelect
  ProcedureReturn result
EndProcedure


OpenWindow(0, 0, 0, 270, 140, "ComboBoxGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  hWnd = ComboBoxGadget(0, 10, 10, 250, 21, #PB_ComboBox_Editable)
  hWnd = GetWindow_(hWnd, #GW_CHILD)
  hWndEdit = FindWindowEx_(hWnd, 0, "EDIT", 0)
  oldcomboproc = SetWindowLong_(hWndEdit, #GWL_WNDPROC, @ComboAutoComplete())
  AddGadgetItem(0, -1, "12345")
  AddGadgetItem(0, -1, "23/468")
  AddGadgetItem(0, -1, "483~47")
  AddGadgetItem(0, -1, "58237")
  AddGadgetItem(0, -1, "58.772")
SetActiveGadget(0)


Repeat
  W=WaitWindowEvent()
Until W = #PB_Event_CloseWindow

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Mon Apr 12, 2010 5:30 pm
by ThoPie
Wow! Thanks. :lol:

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Mon Apr 12, 2010 5:44 pm
by USCode
How about a cross-platform version??? Possible?

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Mon Apr 12, 2010 9:27 pm
by mesozorn
srod wrote:GetComboBoxInfo_() is not reliable for the new combos. I have replaced this and the example seems to work ok.
Out of curiosity, why is it that this API command will no longer work on a ComboBoxGadget under PB 4.5? GetComboBoxInfo_() is a standard Windows command, so shouldn't it always work on a standard Windows combo control?

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Mon Apr 12, 2010 9:48 pm
by ts-soft
ComboBox is changed to ComboBoxEx to support images!

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Mon Apr 12, 2010 10:04 pm
by mesozorn
ts-soft wrote:ComboBox is changed to ComboBoxEx to support images!
Ah I see, in which case is there going to be any support for either of:

SendMessage_(hWnd,#CBEM_GETCOMBOCONTROL,0,0)
SendMessage_(hWnd,#CBEM_GETEDITCONTROL,0,0)

? Right now neither of those API constants appear to be recognized..? They would be helpful in either extracting the standard ComboBox portion of the ComboBoxEx, or getting the edit control handle directly. Is there any way of finding out what those numerical constants would be and declaring them oneself, even if they are not yet included in PB by default? Thanks...

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Sun Jun 05, 2011 11:54 am
by Fangbeast

Code: Select all

Global oldcomboProc

Procedure.i ComboAutoComplete(hWnd, uMsg, wParam, lParam)
  Protected result
  Select uMsg
    Case #WM_CHAR
    acg=GetActiveGadget() ;GetDlgCtrlID_(hwnd)
    SendMessage_(hwnd, #EM_GETSEL, @spos.l, @epos.l)
    matchesfound=0 
      For x=0 To CountGadgetItems(acg)-1
        If LCase(Left(GetGadgetText(acg),spos)+LCase(Chr(wParam)))=LCase(Left(GetGadgetItemText(acg,x),spos+1)) And epos=Len(GetGadgetText(acg))
          matchesfound+1:match=x
          If matchesfound>1:Break:EndIf
        EndIf
      Next x   
      If matchesfound=1 
          ks=GetKeyState_(#VK_SHIFT)
          If ks<2:addchar$=LCase(Chr(wparam)):Else:addchar$=UCase(Chr(wparam)):EndIf     SetGadgetText(acg,Left(GetGadgetText(acg),spos)+addchar$+Mid(GetGadgetItemText(acg,match),spos+2))
          SendMessage_(hwnd, #EM_SETSEL, spos+1, epos+999)
          result=0
      Else
        result = CallWindowProc_(oldcomboproc, hWnd, uMsg, wParam, lParam)
      EndIf 
    Default
      result = CallWindowProc_(oldcomboproc, hWnd, uMsg, wParam, lParam)
  EndSelect
  ProcedureReturn result
EndProcedure

OpenWindow(0, 0, 0, 270, 140, "ComboBoxGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  hWnd = ComboBoxGadget(0, 10, 10, 250, 21, #PB_ComboBox_Editable)
  hWnd = GetWindow_(hWnd, #GW_CHILD)
  hWndEdit = FindWindowEx_(hWnd, 0, "EDIT", 0)
  oldcomboproc = SetWindowLong_(hWndEdit, #GWL_WNDPROC, @ComboAutoComplete())
  AddGadgetItem(0, -1, "12345")
  AddGadgetItem(0, -1, "23/468")
  AddGadgetItem(0, -1, "483~47")
  AddGadgetItem(0, -1, "58237")
  AddGadgetItem(0, -1, "58.772")
SetActiveGadget(0)
Repeat
  W=WaitWindowEvent()
Until W = #PB_Event_CloseWindow
I'm afraid to touch working code that I don't understand so I am wondering how this can be extended to cover multiple comboboxes as oldcomboproc is only for a single combo box as I understand it??

Re: Autocomplete for Comboboxgadget - Simple version

Posted: Sun Jun 05, 2011 12:52 pm
by netmaestro
oldcomboproc is only for a single combo box as I understand it??
'oldcomboproc' is going to be the same for all comboboxes you subclass as it's the default combobox procedure. You can safely use this with multiple combo boxes.