IAutoComplete for the COM freaks

Share your advanced PureBasic knowledge/code with the community.
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

IAutoComplete for the COM freaks

Post by Justin »

This will show you how to use the IAutoComplete object, the one that expands an edit control with strings like in the IE URL.

The 1st code chunk are list functions of my own, it's just a small set of an include i use. the names an behaviour is inspired by the C++ CObList class, there aren't current elements, nor indexs.. But you could probably use a PB list.

The 2nd code is the example itself.

I haven't implemented the Skip() and Clone() methods because i think they are not used but this is just an assumption you should check it.

You are supposed to type something in the edit box if there are similar strings in the string list it will be expanded with string candidates.

Code: Select all

;- Linked list
Structure _LL_ELEMENT_
	*pNext._LL_ELEMENT_
	*pPrev._LL_ELEMENT_
	
	*elData.l
EndStructure

Structure _LL_DATA_
	*pFirst._LL_ELEMENT_	
	*pLast._LL_ELEMENT_
	size.l			;number of elements in list
	elSize.l		;element size (not including extra pointers pNext, pPrev)
EndStructure

Procedure liGetNext(*ppEl.LONG)
	*pEl._LL_ELEMENT_ = *ppEl\l - OffsetOf(_LL_ELEMENT_\elData)
	
	If *pEl\pNext
		*ppEl\l = *pEl\pNext + OffsetOf(_LL_ELEMENT_\elData)
	Else
		*ppEl\l = #NULL
	EndIf 
EndProcedure 

Procedure liGetHead(*pList._LL_DATA_)
	If *pList\pFirst
		ProcedureReturn @*pList\pFirst\elData
	Else
		ProcedureReturn #NULL
	EndIf 
EndProcedure

Procedure liNew(elSize)
	*pList._LL_DATA_ = AllocateMemory(SizeOf(_LL_DATA_))

	*pList\size = 0
	*pList\elSize = elSize
	ProcedureReturn *pList
EndProcedure

Procedure liAddTail(*pList._LL_DATA_)
	*pNewEl._LL_ELEMENT_ = AllocateMemory(OffsetOf(_LL_ELEMENT_\elData) + *pList\elSize)

	If *pList\size=0 ;no items, add as first
		*pList\pFirst = *pNewEl
		*pList\pLast = *pNewEl
		
	Else ;Items, add after last
		*pNewEl\pPrev = *pList\pLast
		*pList\pLast\pNext = *pNewEl
		
		*pList\pLast = *pNewEl
	EndIf 
	
	*pList\size + 1
	ProcedureReturn @*pNewEl\elData
EndProcedure 

Code: Select all

;- IAutoComplete
#CLSCTX_INPROC_SERVER        = $1

#ACO_NONE	= 0
#ACO_AUTOSUGGEST	= $1
#ACO_AUTOAPPEND	= $2
#ACO_SEARCH	= $4
#ACO_FILTERPREFIXES	= $8
#ACO_USETAB	= $10
#ACO_UPDOWNKEYDROPSLIST	= $20
#ACO_RTLREADING	= $40

Interface _IEnumString Extends IEnumString
	put_List(*hList._LL_DATA_)
	get_List()
	_ClearList()
	DestroyList()
EndInterface 

;Creates an unicode string from an ansi string, returns pointer to unicode string
;String memory needs to be freed by the caller
Procedure cWstr(st$)
	blen = (lstrlen_(st$) * 2) + 2
	wbuf = AllocateMemory(blen)
	
	MultiByteToWideChar_(#CP_ACP, 0, st$, -1, wbuf, blen)
	ProcedureReturn wbuf
EndProcedure 

Structure IEnumString_VT
	;IEnumString
  QueryInterface.l 
  AddRef.l 
  Release.l 
  _Next.l
  Skip.l
  Reset.l
  Clone.l
  
  ;Obj own functions
  put_List.l
  get_List.l
  _ClearList.l
  DestroyList.l
EndStructure 

Structure IEnumString_OBJ
  *vt.IEnumString_VT

	objCount.l
	*hstList._LL_DATA_	;string list handle (list of pointers to unicode strings)
	*strCurr.LONG				;pointer to current list element
EndStructure 

;- IUnknown
Procedure est_QueryInterface(*THIS.IEnumString_OBJ, *iid.GUID, *Object.LONG) 
  If CompareMemory(*iid, ?IID_IUnknown, SizeOf(GUID)) Or CompareMemory(*iid, ?IID_IEnumString, SizeOf(GUID)) 
    *Object\l = *THIS
    *THIS\objCount + 1
    ProcedureReturn #S_OK 
  Else    
    *Object\l = 0 
    ProcedureReturn #E_NOINTERFACE 
  EndIf 
EndProcedure 

Procedure.l est_AddRef(*THIS.IEnumString_OBJ) 
  *THIS\objCount + 1 
  ProcedureReturn *THIS\objCount 
EndProcedure 

Procedure.l est_Release(*THIS.IEnumString_OBJ) 
  *THIS\objCount - 1 
  ProcedureReturn *THIS\objCount 
EndProcedure 

;- IEnumString
Procedure est_Next(*THIS.IEnumString_OBJ, celt.l, *rgelt.LONG, *pceltFetched.LONG)	
	If *THIS\strCurr ;not last	
		*rgelt\l = *THIS\strCurr\l
		*pceltFetched\l = 1		
		liGetNext(@*THIS\strCurr)
		ProcedureReturn #S_OK
	Else
		*pceltFetched\l = 0
		ProcedureReturn #S_FALSE 
	EndIf 
EndProcedure 

Procedure est_Skip(*THIS.IEnumString_OBJ, celt.l)
	ProcedureReturn #S_FALSE
EndProcedure 

Procedure est_Reset(*THIS.IEnumString_OBJ)
	*THIS\strCurr = liGetHead(*THIS\hstList)
	ProcedureReturn #S_OK
EndProcedure 

Procedure est_Clone(*THIS.IEnumString_OBJ, *ppenum.l)
	ProcedureReturn #E_UNEXPECTED
EndProcedure 

;Sets the object string list
Procedure est_put_List(*THIS.IEnumString_OBJ, hstList.l) : *THIS\hstList = hstList : EndProcedure 

;Gets the object string list
Procedure est_get_List(*THIS.IEnumString_OBJ) : ProcedureReturn *THIS\hstList : EndProcedure 

;Frees the object string list elements and sets the current string element to null,
;leaves the string list handle valid.
Procedure est_ClearList(*THIS.IEnumString_OBJ)
	*pELdat.LONG ;pointer to element data
	*pEl.l ;pointer to list element
	
	If *THIS\hstList
		*pELdat = liGetHead(*THIS\hstList)
		While *pELdat
			*pEl = *pEldat - OffsetOf(_LL_ELEMENT_\elData) ;save pointer for deletion
			
			FreeMemory(*pELdat\l) ;free unicode string
			
			liGetNext(@*pELdat)

			FreeMemory(*pEl) ;free element
		Wend 
		
		*THIS\hstList\size = 0
		*THIS\hstList\pFirst = #NULL
		*THIS\hstList\pLast = #NULL
		
		*THIS\strCurr = #NULL
	EndIf 
EndProcedure 

;Destroys the string list invalidating the handle
Procedure est_DestroyList(*THIS.IEnumString_OBJ)
	oest._IEnumString = *THIS
	
	If *THIS
		oest\_ClearList()
		FreeMemory(*THIS\hstList)
	EndIf
EndProcedure

;EnumString Object constructor
Procedure CEnumString()
	*obj.IEnumString_OBJ = allocatememory(SizeOf(IEnumString_OBJ))
	*obj\vt = AllocateMemory(SizeOf(IEnumString_VT))
		
	*obj\vt\QueryInterface = @est_QueryInterface()
	*obj\vt\AddRef = @est_AddRef()
	*obj\vt\Release = @est_Release()
	
	*obj\vt\_Next = @est_Next()
	*obj\vt\Skip = @est_Skip()
	*obj\vt\Reset = @est_Reset()
	*obj\vt\Clone = @est_Clone()
	
	*obj\vt\put_List = @est_put_List()
	*obj\vt\get_List = @est_get_List()
	*obj\vt\_ClearList = @est_ClearList()
	*obj\vt\DestroyList = @est_DestroyList()

	ProcedureReturn *obj 
EndProcedure 

;EnumString Object destructor
;Frees object memory. It does not free string list memory.
Procedure DEnumString(*obj.IEnumString_OBJ)
	If *obj
		If *obj\vt : FreeMemory(*obj\vt) : EndIf
		FreeMemory(*obj)  
	EndIf 
EndProcedure 

;- #CODE

;Init COM
CoInitialize_(0)

;Create window
OpenWindow(0,0,0,322,275,#PB_Window_SystemMenu|#PB_Window_ScreenCentered,"IAutoComplete") And CreateGadgetList(WindowID(0))
hwED = StringGadget(0, 8, 10, 306, 20, "")

;- Create object that exposes EnumString
oest._IEnumString = CEnumString()

;Make a list of pointers to unicode strings
*pl.LONG ;List element

hstl = liNew(SizeOf(LONG))
*pl = liAddTail(hstl) : *pl\l = cwstr("Red")
*pl = liAddTail(hstl) : *pl\l = cwstr("Green")
*pl = liAddTail(hstl) : *pl\l = cwstr("Blue")
*pl = liAddTail(hstl) : *pl\l = cwstr("White")
*pl = liAddTail(hstl) : *pl\l = cwstr("Yellow")
*pl = liAddTail(hstl) : *pl\l = cwstr("Black")

;Set the object list
oest\put_List(hstl)

;- Create AutoComplete object
r = CoCreateInstance_(?CLSID_AutoComplete, #NULL, #CLSCTX_INPROC_SERVER, ?IID_IAutoComplete, @oac.IAutoComplete2)
If r<>#S_OK ;error
	End
EndIf 

;Setup AutoComplete
oac\SetOptions(#ACO_AUTOSUGGEST)
oac\Init(hwED, oest, 0, 0)

;Msg loop
fQuit = 0
Repeat 
	EvID = WaitWindowEvent()
	If EvID=#PB_Event_CloseWindow
		fQuit=1
	EndIf 
Until fQuit

;Free
oac\Release()
oest\DestroyList()
DEnumString(oest)

CoUnInitialize_()
End

;- #DATA
DataSection
	IID_IEnumString:
	Data.l $00000101 
	Data.w $0000, $0000 
	Data.b $C0, $00, $00, $00, $00, $00, $00, $46 
	
	IID_IAutoComplete:
	Data.l $00bb2762 
	Data.w $6a77, $11d0 
	Data.b $a5, $35, $00, $c0, $4f, $d7, $d0, $62
	
	CLSID_AutoComplete:
	Data.l $00BB2763 
	Data.w $6a77, $11d0 
	Data.b $a5, $35, $00, $c0, $4f, $d7, $d0, $62
	
	IID_IUnknown:  
	Data.l $00000000 
	Data.w $0000, $0000 
	Data.b $C0, $00, $00, $00, $00, $00, $00, $46 
EndDataSection  
Nico
Enthusiast
Enthusiast
Posts: 274
Joined: Sun Jan 11, 2004 11:34 am
Location: France

Post by Nico »

PB don't know this structure:_LL_DATA_ :?:
Tranquil
Addict
Addict
Posts: 949
Joined: Mon Apr 28, 2003 2:22 pm
Location: Europe

Post by Tranquil »

Code crashes on quit on my win 2000 system.
Tranquil
benny
Enthusiast
Enthusiast
Posts: 465
Joined: Fri Apr 25, 2003 7:44 pm
Location: end of www
Contact:

Post by benny »

Here is an autocomplete mechanism for paths by hm (german forum).

To test it, just type e.g. "C:\" into the textfield ... to see what it does.

Code: Select all


; SHAutoComplete Beispiel
; hm
; 031127
;  Die SHAutoComplete Function befindet sich in shlwapi.dll
;  LWSTDAPI SHAutoComplete(hwnd hwndedit, DWORD dwFlags);

; ab IE5:
#SHACF_DEFAULT           = $00000000  ;// Currently (SHACF_FILESYSTEM | SHACF_URLALL)
#SHACF_FILESYSTEM        = $00000001  ;// This includes the File System as well as the rest of the shell (Desktop\My Computer\Control Panel\)
#SHACF_URLALL            = ( $00000002 | $00000004 )    ; (#SHACF_URLHISTORY | #SHACF_URLMRU)
#SHACF_URLHISTORY        = $00000002  ;// URLs in the User's History
#SHACF_URLMRU            = $00000004  ;// URLs in the User's Recently Used list.
#SHACF_USETAB            = $00000008  ;// Use the tab To move thru the autocomplete possibilities instead of To the Next dialog/window Control.
#SHACF_FILESYS_ONLY      = $00000010  ;// This includes the File System

#SHACF_AUTOSUGGEST_FORCE_ON  = $10000000  ;// Ignore the registry Default And force the feature on.
#SHACF_AUTOSUGGEST_FORCE_OFF = $20000000  ;// Ignore the registry Default And force the feature off.
#SHACF_AUTOAPPEND_FORCE_ON   = $40000000  ;// Ignore the registry Default And force the feature on. (Also know as autocomplete)
#SHACF_AUTOAPPEND_FORCE_OFF  = $80000000  ;// Ignore the registry Default And force the feature off. (Also know as autocomplete)
; ab IE6:
#SHACF_FILESYS_DIRS      = $00000020  ;// Same as SHACF_FILESYS_ONLY except it only includes directories, UNC servers, And UNC server shares.
 
 
Enumeration
  #Window_Main
EndEnumeration

Enumeration
  #String_AutoComplete
EndEnumeration


If OpenWindow(#Window_Main, 217, 150, 292, 40,  #PB_Window_SystemMenu | #PB_Window_TitleBar , "SHAutoComplete Test")
  If CreateGadgetList(WindowID())
    StringGadget(#String_AutoComplete, 10, 10, 270, 20, "")

    CoInitialize_(#NULL)
    If OpenLibrary(0,"shlwapi.dll")
      func_shautocomplete.l = IsFunction(0,"SHAutoComplete")
      If func_shautocomplete <> #NULL
        If CallFunctionFast(func_shautocomplete, GadgetID(#String_AutoComplete), ( #SHACF_AUTOAPPEND_FORCE_ON | #SHACF_AUTOSUGGEST_FORCE_ON | #SHACF_FILESYSTEM ) ) = #S_OK
        Else
          MessageRequester("error","Fehler bei SHAutoComplete()")
        EndIf
      Else
        MessageRequester("error","Funktion SHAutoComplete nicht gefunden in shlwapi.dll");
      EndIf
      CloseLibrary(0)
    Else
      MessageRequester("error","Fehler beim Öffnen von shlwapi.dll")
    EndIf

  EndIf
EndIf

Repeat : Until WaitWindowEvent() = #PB_EventCloseWindow

CoUninitialize_()

End 
regards,
benny!
-
pe0ple ar3 str4nge!!!
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Post by Justin »

_LL_DATA_ is defined in the 1st code part

i'm interested in the crash, can you try invalidating DEnumString(oest) in the free part? i suspect it comes from here althoug it works on XP
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Post by Justin »

i think i know what happened, in the constructor you have to set the object reference count to 1

Code: Select all

Procedure CEnumString()
	*obj.IEnumString_OBJ = AllocateMemory(SizeOf(IEnumString_OBJ))
	*obj\vt = AllocateMemory(SizeOf(IEnumString_VT))
		
	*obj\vt\QueryInterface = @est_QueryInterface()
	*obj\vt\AddRef = @est_AddRef()
	*obj\vt\Release = @est_Release()
	
	*obj\vt\_Next = @est_Next()
	*obj\vt\Skip = @est_Skip()
	*obj\vt\Reset = @est_Reset()
	*obj\vt\Clone = @est_Clone()
	
	*obj\vt\put_List = @est_put_List()
	*obj\vt\get_List = @est_get_List()
	*obj\vt\_ClearList = @est_ClearList()
	*obj\vt\DestroyList = @est_DestroyList()
	
	*obj\objCount = 1

	ProcedureReturn *obj 
EndProcedure 
then you destroy the object in the Release() method when the count reaches 0, you'll need to declare DEnumString() first

Code: Select all

Procedure.l est_Release(*THIS.IEnumString_OBJ) 
  *THIS\objCount - 1 
   If *THIS\objCount = 0
  	DEnumString(*THIS)
  	Debug "IEnumString Released"
  EndIf 
  ProcedureReturn *THIS\objCount 
EndProcedure 
and you free the objects like this at the end

Code: Select all

;Free
oac\Release() 
oest\DestroyList()
oest\Release()
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Post by Justin »

continues to crash when creating an exe :D

you have to use closewindow() before freeing or probably free in the WM_DESTROY message because i guess the edit control has to exist before freeing IAutocomplete

with the previuos change and freeing like this works,

Code: Select all

;Free
CloseWindow(0)
oac\Release() ;Release IAutoComplete
oest\DestroyList()
oest\Release()
Post Reply