Page 3 of 3

Posted: Thu Apr 24, 2008 6:25 pm
by Karbon
Hey Xombie - have you done any further updates to your code?

Since I switched to the WinAPI implementation Vista users have complained about it locking up the software. I've verified that and am looking for other solutions now.

Posted: Mon Apr 28, 2008 9:24 pm
by Xombie
Karbon,

I've been using this revision for my current personal project but I've only updated the combobox part and not the string/editor part. Let me know if there's anything wrong with it and I can update it fairly quickly for you.

Code: Select all

;- Coded by Xombie 12/19/2005, updated 8/18/2006, updated 12/11/2007
;- Structures 
Structure s_ACStringStructure 
  FakeString.s[0] 
EndStructure 
Structure s_AutoComplete 
   ;
   Gadget.l 
   Handle.l 
   Parent.l 
   CallBack.l 
   ; The old callback handle. 
   ArrayAddress.l 
   ; Only valid for edit controls, this will be the address of the first element in an array of strings. 
   PopList.b
   ; Non-zero if displaying a small popup list of matching text.  The value is the number of characters to match on.  This
   ; completely changes the behavior of the autocompletion routines.  Individual words are matched to a list of words.
   ShowDrop.b
   ;
EndStructure 
Structure _s_AutoComplete_Info
   ;
   Gadget.l
   ;
   IdentifierWindow.l
   HandleWindow.l
   ;
   IdentifierList.l
   HandleList.l
   ;
   CallBack.l
   ;
EndStructure
;-
EnableExplicit
;- Global Variables 
Global NewList _ac_Main.s_AutoComplete() 
Global __ac_Info._s_AutoComplete_Info
;-
Procedure _ac_QuickSortArray(Address.l, g.l, d.l)
   ; Modified from the PureBasic advanced examples.
   Define.s v, tmp, t
   ;
   Define.l i, j, ok
   ;
   Define.s_ACStringStructure *Position
   ; This will be a pointer to the strings in the array. 
   *Position = Address
   ;
   If g < d
      v = *Position\FakeString[d]
      i = g-1
      j = d
      ;
      Repeat
         Repeat : i=i+1 : Until LCase(*Position\FakeString[i]) >= LCase(v)
         ok = 0
         Repeat
            If j>0 : j=j-1 : Else : ok=1 : EndIf 
            If LCase(*Position\FakeString[j]) <= LCase(v) : ok=1 : EndIf
         Until ok<>0 
         tmp = *Position\FakeString[i]
         *Position\FakeString[i] = *Position\FakeString[j]
         *Position\FakeString[j] = tmp
      Until j <= i
      ;
      t = *Position\FakeString[j]
      *Position\FakeString[j] = *Position\FakeString[i]
      *Position\FakeString[i] = *Position\FakeString[d]
      *Position\FakeString[d] = t
      ;
      _ac_QuickSortArray(Address, g, i-1)
      _ac_QuickSortArray(Address, i+1, d)
      ;
   EndIf
   ;
EndProcedure
Procedure _ac_KillList()
   ;
   If __ac_Info\IdentifierWindow
      ;
      FreeGadget(__ac_Info\IdentifierList)
      ;
      CloseWindow(__ac_Info\IdentifierWindow)
      ;
      __ac_Info\IdentifierWindow = 0
      ;
      __ac_Info\HandleWindow = 0
      ;
      __ac_Info\IdentifierList = 0
      ;
      __ac_Info\HandleList = 0
      ;
   EndIf
   ;
EndProcedure
Procedure.l _ac_HandleListEvents(HandleWindow.l, Message.l, wParam.l, lParam.l) 
   ;
   Define.l lResult
   ;
   Define.l HoldLength
   ;
   Define.s HoldString
   ;
   Define.l *Position
   ;
   Define.l Type
   ;
   Define.CHARRANGE HoldRange
   ;
   Type = GadgetType(_ac_Main()\Gadget)
   ;
   If Message = #WM_LBUTTONDBLCLK
      ;
      HoldString = GetGadgetText(_ac_Main()\Gadget) 
      ; Store the current text. 
      HoldLength = Len(HoldString) 
      ;
      *Position = @HoldString + HoldLength - 1
      ;
      While *Position > @HoldString
         ;
         If PeekB(*Position) < 33 : Break : EndIf
         ;
         *Position - 1
         ;
      Wend
      ;
      If Type = #PB_GadgetType_Editor : SendMessage_(_ac_Main()\Handle, #EM_HIDESELECTION, #True, #Null) : EndIf
      ;
      If PeekB(*Position) < 33
         ;
         If Type = #PB_GadgetType_String
            SendMessage_(_ac_Main()\Handle, #EM_SETSEL, *Position - @HoldString + 1, HoldLength)
         Else
            HoldRange\cpMin = *Position - @HoldString + 1 : HoldRange\cpMax = HoldLength
            SendMessage_(_ac_Main()\Handle, #EM_EXSETSEL, *Position - @HoldString + 1, HoldLength)
         EndIf
         ;
      Else
         ;
         If Type = #PB_GadgetType_String
            SendMessage_(_ac_Main()\Handle, #EM_SETSEL, *Position - @HoldString, HoldLength)
         Else
            HoldRange\cpMin = *Position - @HoldString : HoldRange\cpMax = HoldLength
            SendMessage_(_ac_Main()\Handle, #EM_EXSETSEL, *Position - @HoldString, HoldLength)
         EndIf
         ;
      EndIf
      ;
      HoldString = GetGadgetText(__ac_Info\IdentifierList) + " "
      ;
      SendMessage_(_ac_Main()\Handle, #EM_REPLACESEL, #True, @HoldString)
      ;
      If Type = #PB_GadgetType_Editor : SendMessage_(_ac_Main()\Handle, #EM_HIDESELECTION, #False, #Null) : EndIf
      ;
      _ac_KillList()
      ;
      SetActiveGadget(__ac_Info\Gadget)
      ;
      ProcedureReturn 1
      ;
   EndIf
   ;
   lResult = CallWindowProc_(__ac_Info\CallBack, HandleWindow, Message, wParam, lParam)
   ;
   ProcedureReturn lResult
   ;
EndProcedure
;- Popup Autocomplete
Procedure.l _ac_PopList(HoldComplete.s_AutoComplete(), ItemCount.l, Text.s)
   ;
   Define.l iLoop, jLoop
   ;
   Define.l lResult
   ;
   Define.l Parent
   ;
   Define.l lHold
   ;
   Define.s HoldString, HoldText
   ;
   Define.l HoldLength
   ;
   Define.l HoldHeight
   ;
   Define.l CharacterPosition
   ;
   Define.l *Position
   ;
   Define.l iCount
   ;
   Define.b CaughtMatch
   ;
   Define.POINT HoldPoint
   ;
   Define.s_ACStringStructure *HoldArray
   ; This will be a pointer to the strings in the array. 
   HoldLength = Len(Text)
   ;
   *Position = @Text + HoldLength - 1
   ;
   If PeekB(*Position) = 32 : _ac_KillList() : EndIf
   ; Only autocompleting single words.
   While *Position > @Text
      ;
      If PeekB(*Position) < 33 : Break : EndIf
      ;
      *Position - 1
      ;
   Wend
   ;
   If PeekB(*Position) < 33
      ;
      If PeekS(*Position + 1) = ""
         ;
         _ac_KillList()
         ;
         ProcedureReturn
         ;
      EndIf
      ;
      CharacterPosition = *Position - @Text + 2
      ;
      HoldText = LCase(PeekS(*Position + 1))
      ;
   Else
      ;
      If PeekS(*Position) = ""
         ;
         _ac_KillList()
         ;
         ProcedureReturn
         ;
      EndIf
      ;
      CharacterPosition = *Position - @Text
      ; Subtract one to make zero based.
      HoldText = LCase(PeekS(*Position))
      ;
   EndIf
   ;
   HoldLength = Len(HoldText)
   ;
   If HoldLength < HoldComplete()\PopList
      ;
      _ac_KillList()
      ;
      ProcedureReturn
      ;
   EndIf
   ;
   *HoldArray = HoldComplete()\ArrayAddress
   ;
   While iLoop <= ItemCount
      ; Loop through the list of items.
      HoldString = LCase(*HoldArray\FakeString[iLoop])
      ; Store the text at index iLoop.  This little trick of getting the items in a array by addresses is thanks (again) to freak ( http://forums.purebasic.com/english/viewtopic.php?t=15366 ) 
      If HoldText = Left(HoldString, HoldLength)
         ;
         If HoldText = HoldString : CaughtMatch = #True : EndIf
         ;
         HoldHeight + 1
         ;
         If HoldHeight = 5 : Break : EndIf
         ;
      EndIf
      ;
      iLoop + 1
      ;
   Wend
   ;
   If HoldHeight = 0 Or (HoldHeight = 1 And CaughtMatch)
      ;
      _ac_KillList()
      ;
      ProcedureReturn
      ;
   EndIf
   ;
   __ac_Info\Gadget = HoldComplete()\Gadget
   ;
   If __ac_Info\IdentifierWindow
      ; The popup window already exists.
      ClearGadgetItemList(__ac_Info\IdentifierList)
      ; Remove all current items from the list.
      iLoop = 0
      ;
      While iLoop <= ItemCount
         ; Loop through the list of items.
         If HoldText = LCase(Left(*HoldArray\FakeString[iLoop], HoldLength))
            ; This little trick of getting the items in a array by addresses is thanks (again) to freak ( http://forums.purebasic.com/english/viewtopic.php?t=15366 ) 
            CaughtMatch = #False
            ;
            iCount = CountGadgetItems(__ac_Info\IdentifierList)
            For jLoop = 1 To iCount
               ;
               If LCase(*HoldArray\FakeString[iLoop]) = LCase(GetGadgetItemText(__ac_Info\IdentifierList, jLoop - 1, 0)) : CaughtMatch = #True : Break : EndIf
               ;
            Next jLoop
            ;
            If CaughtMatch = #False : AddGadgetItem(__ac_Info\IdentifierList, -1, *HoldArray\FakeString[iLoop]) : EndIf
            ;
         EndIf
         ;
         iLoop + 1
         ;
      Wend 
      ;
      ResizeWindow(__ac_Info\IdentifierWindow, #PB_Ignore, #PB_Ignore, #PB_Ignore, (HoldHeight * 14) + 4)
      ;
      ResizeGadget(__ac_Info\IdentifierList, #PB_Ignore, #PB_Ignore, #PB_Ignore, (HoldHeight * 14) + 1)
      ;
   Else
      ; The popup window does not exist.
      lResult = SendMessage_(HoldComplete()\Handle, #EM_POSFROMCHAR, @HoldPoint, CharacterPosition)
      ;
      lHold = GetParent_(HoldComplete()\Handle) : While lHold : Parent = lHold : lHold = GetParent_(lHold) : Wend
      ; Get the handle to the parent window of the edit control.
      lResult = MapWindowPoints_(HoldComplete()\Handle, lHold, @HoldPoint, 1)
      ; Convert the coordinates to the main window.
      __ac_Info\IdentifierWindow = OpenWindow(#PB_Any, 0, 0, 244, (HoldHeight * 14) + 4, "", #PB_Window_WindowCentered | #PB_Window_BorderLess, Parent)
      ;
      If __ac_Info\IdentifierWindow
         ;
         __ac_Info\HandleWindow = WindowID(__ac_Info\IdentifierWindow)
         ;
         If CreateGadgetList(__ac_Info\HandleWindow)
            ;
            __ac_Info\IdentifierList = ListViewGadget(#PB_Any, 2, 2, 240, (HoldHeight * 14) + 1)
            ;
            __ac_Info\HandleList = GadgetID(__ac_Info\IdentifierList)
            ;
            __ac_Info\CallBack = SetWindowLong_(__ac_Info\HandleList, #GWL_WNDPROC, @_ac_HandleListEvents()) 
            ;
            iLoop = 0
            ;
            While iLoop <= ItemCount
               ; Loop through the list of items.
               If HoldText = LCase(Left(*HoldArray\FakeString[iLoop], HoldLength))
                  ; This little trick of getting the items in a array by addresses is thanks (again) to freak ( http://forums.purebasic.com/english/viewtopic.php?t=15366 ) 
                  CaughtMatch = #False
                  ;
                  iCount = CountGadgetItems(__ac_Info\IdentifierList)
                  For jLoop = 1 To iCount
                     ;
                     If LCase(*HoldArray\FakeString[iLoop]) = LCase(GetGadgetItemText(__ac_Info\IdentifierList, jLoop - 1, 0)) : CaughtMatch = #True : Break : EndIf
                     ;
                  Next jLoop
                  ;
                  If CaughtMatch = #False : AddGadgetItem(__ac_Info\IdentifierList, -1, *HoldArray\FakeString[iLoop]) : EndIf
                  ;
               EndIf
               ;
               iLoop + 1
               ;
            Wend 
            ;
         Else
            ;
            ProcedureReturn
            ;
         EndIf
         ;
      Else
         ;
         ProcedureReturn
         ;
      EndIf
      ;
      ResizeWindow(__ac_Info\IdentifierWindow, HoldPoint\X, HoldPoint\Y + 14, #PB_Ignore, #PB_Ignore)
      ;
   EndIf
   ;
   SetGadgetState(__ac_Info\IdentifierList, 0)
   ;
   SetActiveGadget(HoldComplete()\Gadget)
   ;
EndProcedure
;- Callback Functions 
Procedure.l _ac_HandleEditEvents(HandleWindow.l, Message.l, wParam.l, lParam.l) 
   ; Custom callback for edit controls. 
   Define.l lResult
   ; 
   Define.l iLoop
   ; 
   Define.l HoldTotal
   ; 
   Define.l HoldLength
   ; 
   Define.l HoldCombinedLength
   ; 
   Define.l HoldStart
   ; 
   Define.l HoldEnd
   ;
   Define.CHARRANGE HoldRange
   ;
   Define.s HoldString
   ; 
   Define.s CombinedText
   ; 
   Define.l *Position
   ;
   Define.s_ACStringStructure *HoldArray
   ; This will be a pointer to the strings in the array. 
   Define.l Type
   ;
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      ;
      If _ac_Main()\Handle = HandleWindow : Break : EndIf 
      ;
   Wend 
   ; 
   If _ac_Main()\CallBack = -1 : ProcedureReturn : EndIf 
   ; This should never happen as this callback is only set for autocomplete items. 
   Type = GadgetType(_ac_Main()\Gadget)
   ;
   HoldTotal = PeekL(_ac_Main()\ArrayAddress - 8) 
   ; Return the number of items in the array. 
   If HoldTotal = 0
      ;  No need to complete if 0 items. 
      lResult = CallWindowProc_(_ac_Main()\CallBack, HandleWindow, Message, wParam, lParam)
      ;
   Else
      ;
      *HoldArray = _ac_Main()\ArrayAddress 
      ; Point to the first element in the array. 
      If Message = #WM_DESTROY
         ;
         SetWindowLong_(GadgetID(_ac_Main()\Gadget), #GWL_WNDPROC, _ac_Main()\CallBack)
         ;
         DeleteElement(_ac_Main())
         ;
      ElseIf Message = #WM_CHAR 
         ;
         HoldString = GetGadgetText(_ac_Main()\Gadget) 
         ; Store the current text. 
         HoldLength = Len(HoldString) 
         ;
         If Type = #PB_GadgetType_String
            SendMessage_(HandleWindow, #EM_GETSEL, @HoldStart, @HoldEnd) 
         Else
            SendMessage_(HandleWindow, #EM_EXGETSEL, 0, @HoldRange) 
            HoldStart = HoldRange\cpMin : HoldEnd = HoldRange\cpMax
         EndIf
         ;
         If __ac_Info\HandleWindow
            ;
            If wParam = #VK_TAB Or wParam = #VK_RETURN
               ;
               HoldString = ReplaceString(HoldString, Chr(13)+Chr(10), Chr(13))
               ;
               HoldLength = Len(HoldString)
               ;
               *Position = @HoldString + HoldLength - 1
               ;
               While *Position > @HoldString
                  ;
                  If PeekB(*Position) < 33 : Break : Else : *Position - 1 : EndIf
                  ;
               Wend
               ;
               If Type = #PB_GadgetType_Editor : SendMessage_(_ac_Main()\Handle, #EM_HIDESELECTION, #True, #Null) : EndIf
               ; Temporarily hide the selection of the text.  This will prevent a flicker as the text is replaced.
               If PeekB(*Position) < 33 : *Position + 1 : EndIf
               ;
               If Type = #PB_GadgetType_String
                  SendMessage_(_ac_Main()\Handle, #EM_SETSEL, *Position - @HoldString, HoldLength)
               Else
                  HoldRange\cpMin = *Position - @HoldString : HoldRange\cpMax = HoldLength
                  SendMessage_(_ac_Main()\Handle, #EM_EXSETSEL, 0, @HoldRange)
               EndIf
               ;
               HoldString = GetGadgetText(__ac_Info\IdentifierList) + " "
               ; Add a trailing space to the text.
               SendMessage_(_ac_Main()\Handle, #EM_REPLACESEL, #True, @HoldString)
               ; Replace the text with the completed text.
               If Type = #PB_GadgetType_Editor : SendMessage_(_ac_Main()\Handle, #EM_HIDESELECTION, #False, #Null) : EndIf
               ; Show the selection again.
               _ac_KillList()
               ;
               wParam = 0
               ;
               ProcedureReturn 1
               ;
            EndIf
            ;
         EndIf
         ;
         If wParam = #VK_BACK
            ;
            If Type = #PB_GadgetType_Editor
               CombinedText = LCase(Mid(HoldString, 1, HoldStart) + Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd + 1))
            Else
               CombinedText = LCase(Mid(HoldString, 1, HoldStart - 1) + Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd + 1))
            EndIf
            ;
         Else
            ;
            CombinedText = LCase(Mid(HoldString, 1, HoldStart) + Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd + 1) + Chr(wParam)) 
            ;
         EndIf
         ; 
         HoldCombinedLength = Len(CombinedText) 
         ;
         If HoldCombinedLength = 0
            ;
            SetGadgetText(_ac_Main()\Gadget, "")
            ;
         Else
            ;
            If _ac_Main()\PopList
               ;
               If HoldCombinedLength >= _ac_Main()\PopList
                  ;
                  _ac_PopList(_ac_Main(), HoldTotal - 1, CombinedText)
                  ;
               Else
                  ;
                  _ac_KillList()
                  ;
               EndIf
               ;
            Else
               ;
               For iLoop = 0 To HoldTotal - 1 
                  ; Loop through all items in the array. 
                  If LCase(Left(*HoldArray\FakeString[iLoop], HoldCombinedLength)) = CombinedText 
                     ; Found a matching item in the combobox. This little trick of getting the items in a array by addresses is thanks (again) to freak ( http://forums.purebasic.com/english/viewtopic.php?t=15366 ) 
                     SetGadgetText(_ac_Main()\Gadget, *HoldArray\FakeString[iLoop]) 
                     ;
                     If Type = #PB_GadgetType_String
                        SendMessage_(HandleWindow, #EM_SETSEL, HoldCombinedLength, -1) 
                     Else
                        HoldRange\cpMin = HoldCombinedLength : HoldRange\cpMax = -1
                        SendMessage_(HandleWindow, #EM_SETSEL, 0, @HoldRange) 
                     EndIf
                     ; 
                     wParam = 0 
                     ; 
                     Break 
                     ; Exit the loop. 
                  EndIf 
                  ; 
               Next iLoop 
               ;
            EndIf
            ;
         EndIf
         ;
         If wParam : lResult = CallWindowProc_(_ac_Main()\CallBack, HandleWindow, Message, wParam, lParam) : EndIf 
         ; 
      ElseIf Message = #WM_KEYDOWN
         ;
         If _ac_Main()\PopList And __ac_Info\HandleWindow
            ;
            HoldString = GetGadgetText(_ac_Main()\Gadget) 
            ; Store the current text. 
            If wParam = #VK_ESCAPE
               ;
               _ac_KillList()
               ;
               wParam = 0
               ;
            ElseIf wParam = #VK_UP
               ;
               HoldTotal = GetGadgetState(__ac_Info\IdentifierList)
               ;
               If HoldTotal - 1 > 0 : HoldTotal - 1 : Else : HoldTotal = 0 : EndIf
               ;
               SetGadgetState(__ac_Info\IdentifierList, HoldTotal)
               ;
               wParam = 0
               ; 
            ElseIf wParam = #VK_RETURN
               ; The code for completion will be handled under the WM_CHAR message.
               If __ac_Info\HandleWindow : wParam = 0 : EndIf
               ;
            ElseIf wParam = #VK_RIGHT
               ;
               wParam = 0
               ;
            ElseIf wParam = #VK_DOWN
               ;
               HoldTotal = GetGadgetState(__ac_Info\IdentifierList)
               ;
               If HoldTotal + 1 > CountGadgetItems(__ac_Info\IdentifierList) : HoldTotal = CountGadgetItems(__ac_Info\IdentifierList) : Else : HoldTotal + 1 : EndIf
               ;
               SetGadgetState(__ac_Info\IdentifierList, HoldTotal)
               ;
               wParam = 0
               ;
            ElseIf wParam = #VK_LEFT
               ;
               wParam = 0
               ;
            ElseIf wParam = #VK_HOME
               ;
               SetGadgetState(__ac_Info\IdentifierList, 0)
               ;
               wParam = 0
               ;
            ElseIf wParam = #VK_PRIOR
               ;
               HoldTotal = GetGadgetState(__ac_Info\IdentifierList)
               ;
               If HoldTotal - 5 > 0 : HoldTotal - 5 : Else : HoldTotal = 0 : EndIf
               ;
               SetGadgetState(__ac_Info\IdentifierList, HoldTotal)
               ;
               wParam = 0
               ;
            ElseIf wParam = #VK_NEXT
               ;
               HoldTotal = GetGadgetState(__ac_Info\IdentifierList)
               ;
               If HoldTotal + 5 > CountGadgetItems(__ac_Info\IdentifierList) - 1 : HoldTotal = CountGadgetItems(__ac_Info\IdentifierList) - 1 : Else : HoldTotal + 5 : EndIf
               ;
               SetGadgetState(__ac_Info\IdentifierList, HoldTotal)
               ;
               wParam = 0
               ;
            ElseIf wParam = #VK_END
               ;
               SetGadgetState(__ac_Info\IdentifierList, CountGadgetItems(__ac_Info\IdentifierList) - 1)
               ;
               wParam = 0
               ;
            EndIf
            ;
            SetActiveGadget(_ac_Main()\Gadget)
            ;
         EndIf
         ;
         If wParam : lResult = CallWindowProc_(_ac_Main()\CallBack, HandleWindow, message, wParam, lParam) : EndIf 
         ; 
      ElseIf message = #WM_KILLFOCUS
         ;
         If wParam <> __ac_Info\HandleWindow
            ;
            _ac_KillList()
            ;
            SendMessage_(_ac_Main()\Handle, #EM_SETSEL, -1, 0)
            ;
         EndIf
         ;
         lResult = CallWindowProc_(_ac_Main()\CallBack, HandleWindow, message, wParam, lParam) 
         ;
      Else 
         ; 
         lResult = CallWindowProc_(_ac_Main()\CallBack, HandleWindow, message, wParam, lParam) 
         ; 
      EndIf 
      ;
   EndIf
   ; 
   ProcedureReturn lResult 
   ; 
EndProcedure 
Procedure.l _ac_HandleComboEvents(HandleWindow.l, Message.l, wParam.l, lParam.l) 
   ; Custom callback for combobox controls. 
   Define.l lResult
   ; 
   Define.l CallBack
   ; 
   Define.l Gadget
   ; 
   Define.l Parent
   ; 
   Define.l iLoop
   ; 
   Define.l HoldTotal
   ; 
   Define.l HoldLength
   ; 
   Define.l HoldCombinedLength
   ; 
   Define.l HoldStart
   ; 
   Define.l HoldEnd
   ; 
   Define.l HoldSelection
   ; 
   Define.s HoldString
   ; 
   Define.s PreText
   ; 
   Define.s PostText
   ; 
   Define.s CombinedText
   ; 
   Define.s MatchText
   ; 
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      ;
      If _ac_Main()\Handle = HandleWindow
         ;
         Parent = _ac_Main()\Parent
         ;
         Gadget = _ac_Main()\Gadget
         ;
         CallBack = _ac_Main()\CallBack
         ;
         Break
         ;
      EndIf 
      ;
   Wend 
   ; Retrieve the handle to the combobox and the gadget id. 
   If CallBack = -1 : ProcedureReturn : EndIf 
   ; This should never happen as this callback is only set for autocomplete items. 
   If Message = #WM_DESTROY
      ;
      lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam)
      ; Call the next callback handler.
      SetWindowLong_(GadgetID(_ac_Main()\Gadget), #GWL_WNDPROC, _ac_Main()\CallBack)
      ;
      DeleteElement(_ac_Main())
      ;
   ElseIf Message = #WM_CHAR
      ;
      HoldTotal = SendMessage_(Parent, #CB_GETCOUNT, 0, 0) 
      ; Return the number of items in the combobox. 
      If HoldTotal 
         ; Only need to check for autocompletion if items exist in the combobox. 
         If _ac_Main()\ShowDrop And SendMessage_(GadgetID(Gadget), #CB_GETDROPPEDSTATE, #Null, #Null) = #False : SendMessage_(GadgetID(Gadget), #CB_SHOWDROPDOWN, #True, #Null) : EndIf
         ;
         HoldString = GetGadgetText(Gadget) 
         ; Store the current combobox text. 
         HoldLength = Len(HoldString) 
         ; 
         SendMessage_(Parent, #CB_GETEDITSEL, @HoldStart, @HoldEnd) 
         ; Store the start and end selection values. 
         If wParam = #VK_BACK
            ;
            CombinedText = LCase(Mid(HoldString, 1, HoldStart - 1)) 
            ;
         Else
            ;
            PreText = Mid(HoldString, 1, HoldStart) 
            ; The text before the selection. 
            PostText = Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd) 
            ; The text after the selection. 
            CombinedText = LCase(PreText + PostText + Chr(wParam)) 
            ;
         EndIf
         ; 
         HoldCombinedLength = Len(CombinedText) 
         ; 
         If HoldCombinedLength = 0
            ;
            SetGadgetText(Gadget, "")
            ;
         Else
            ;
            For iLoop = 0 To HoldTotal - 1 
               ; Loop through all items in the combo box. 
               MatchText = GetGadgetItemText(Gadget, iLoop, 0) 
               ; Store the text at index iLoop. 
               If LCase(Left(MatchText, HoldCombinedLength)) = CombinedText 
                  ; Found a matching item in the combobox. 
                  SetGadgetText(Gadget, MatchText) 
                  ; 
                  HoldSelection = HoldCombinedLength | -1 << 16 
                  ; Convert the start and end selection into an lParam.  The start position is the combined length of the pre 
                  ; and post text.  The end is set to -1 to select the rest of the text. 
                  SendMessage_(GadgetID(Gadget), #CB_SETCURSEL, iLoop, #Null)
                  ;
                  SendMessage_(Parent, #CB_SETEDITSEL, 0, HoldSelection) 
                  ; 
                  wParam = 0 
                  ;
                  ; SetGadgetState(Gadget, iLoop)
                  Break 
                  ; Exit the loop. 
               EndIf 
               ; 
            Next iLoop 
            ;
         EndIf
         ; 
         If wParam : lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) : EndIf 
         ; 
      Else 
         ; 
         lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) 
         ; 
      EndIf 
      ; 
   Else 
      ; 
      lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) 
      ; 
   EndIf 
   ;
   If lResult : ProcedureReturn lResult : EndIf
   ; 
EndProcedure 
;- Main Functions 
Procedure ac_SetAutocomplete(Gadget.l, ShowDrop.b = #False, EditGadgetArrayAddress.l = 0, PopList.b = #False) 
   ; EditGadgetArrayAddress is exactly that.  The address to the first element of a plain string array.  This will be used 
   ; for the autocompletion list.  Feel free to modify this to handle linked lists or whatever.  It will be ignored for 
   ; combobox. See s_AutoComplete for information on PopList.
   Define.l Handle
   ;
   Define.s HoldString
   ;
   Define.l Type
   ;
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      If _ac_Main()\Gadget = Gadget : ProcedureReturn : EndIf 
   Wend 
   ; Check if the gadget already exists in the autocomplete list. 
   Handle = GadgetID(Gadget) 
   ;
   If GadgetType(Gadget) = #PB_GadgetType_Editor Or GadgetType(Gadget) = #PB_GadgetType_String
      ; String type. 
      If EditGadgetArrayAddress
         ; An edit control must be associated with an array address. 
         AddElement(_ac_Main()) 
         ;
         _ac_Main()\Gadget = Gadget 
         _ac_Main()\Handle = Handle 
         ; The handle to the edit control. 
         _ac_Main()\Parent = 0 
         ; An edit control is not like a combobox.  Does not have a parent container. 
         _ac_Main()\CallBack = SetWindowLong_(Handle, #GWL_WNDPROC, @_ac_HandleEditEvents()) 
         ; The callback will be set for the edit control, not the combobox. 
         _ac_QuickSortArray(EditGadgetArrayAddress, 0, PeekL(EditGadgetArrayAddress - 8))
         ;
         _ac_Main()\ArrayAddress = EditGadgetArrayAddress 
         ;
         _ac_Main()\PopList = PopList
         ;
         _ac_Main()\ShowDrop = ShowDrop
         ;
      EndIf
      ;
   ElseIf GadgetType(Gadget) = #PB_GadgetType_ComboBox
      ; Combobox type 
      AddElement(_ac_Main()) 
      ;
      _ac_Main()\Gadget = Gadget 
      _ac_Main()\Handle = GetWindow_(Handle, #GW_CHILD) 
      ; This is the handle to the edit control within the combobox.  I think I got this little tidbit from fr34k but don't quote me on that. 
      _ac_Main()\Parent = Handle 
      ; This will be the handle to the combobox control. 
      _ac_Main()\CallBack = SetWindowLong_(_ac_Main()\Handle, #GWL_WNDPROC, @_ac_HandleComboEvents()) 
      ; The callback will be set for the edit control, not the combobox. 
      _ac_Main()\ShowDrop = ShowDrop
      ;
   EndIf 
   ; 
EndProcedure 
Procedure ac_UpdateArray(Gadget.l, ArrayAddress.l, PopList.b)
   ;
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      ;
      If _ac_Main()\Gadget = Gadget
         ;
         _ac_QuickSortArray(ArrayAddress, 0, PeekL(ArrayAddress - 8))
         ;
         _ac_Main()\ArrayAddress = ArrayAddress 
         ;
         _ac_Main()\PopList = PopList
         ;
         Break
         ;
      EndIf
      ;
   Wend
   ;
EndProcedure

Re: Autocompleting Combobox and Strings

Posted: Sat Mar 06, 2010 3:22 pm
by rsts
Since I can't get the posted version to compile, I'm wondering if there is a newer version now (I'm interested in the string completion feature)?

Or has anyone solved the problems with freaks api code and Vista/Win7?

Or another autocomplete option?

cheers

Re: Autocompleting Combobox and Strings

Posted: Sat Mar 06, 2010 4:13 pm
by netmaestro
Or has anyone solved the problems with freaks api code and Vista/Win7?
I tested freak's code using 4.41 on Win7 and it works fine. There are just a couple tweaks:

1. Put the Array keyword in to overcome the "error in procedure arguments" error in the include. Other than this the include needs no more changes.

2. Remove the CreateGadgetList() from the demo program.

3. To correct the IMA on closing the window, close the window (or free the used gadget) in the demo program before executing the Release() methods:

Code: Select all

      Repeat
      Until WaitWindowEvent() = #PB_Event_CloseWindow
      CloseWindow(#window_0) ; <-------------------------- Insert this line to overcome the IMA in the Release() method
      ; Release our own references to the objects to avoid a memory leak
      ;
      Enum\Release()        
      AutoComplete\Release() 
With these tweaks it should work without problem.

Re: Autocompleting Combobox and Strings

Posted: Sat Mar 06, 2010 6:13 pm
by rsts
Many thanks Mr. netmaestro.

I'll give it a try :)

cheers

Re: Autocompleting Combobox and Strings

Posted: Mon May 07, 2012 8:36 pm
by le_magn
Hi, any update for this Autocomplete by Xombie? old code give me problem, autocomplete not work if i close and reopen the window, or crash if i redim array many time, the last code not compile with pb 4.61, other example of autocomplete in the forum use popup list and for my project is not good, please anyone have a working version of this autocomplete by Xombie?