It is currently Sun Dec 15, 2019 2:59 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 71 posts ]  Go to page 1, 2, 3, 4, 5  Next
Author Message
 Post subject: [Windows] AutoComplete for StringGadgets
PostPosted: Wed Nov 22, 2006 1:39 am 
Offline
PureBasic Team
PureBasic Team
User avatar

Joined: Fri Apr 25, 2003 5:21 pm
Posts: 5783
Location: Germany
I know there is a custom implementation by Xombie here: http://www.purebasic.fr/english/viewtopic.php?t=18310

This code however shows how to use the IAutoComplete interface provided by
windows to do this. It provides an in-place suggestion as well as a drop-down mode.
This requires Windows 2000 or Windows ME btw.

The strings need to be provided through an IEnumString interface.
I created an Includefile to create such an interface easily from an array of strings.
Note that this IEnumString implementation is independant of the rest, so if you need an IEnumString
for some other reason and happen to stumble upon this thread, you found your solution ;)

Example code:
Code:
XIncludeFile "IEnumString.pb"

#ACO_NONE               = 0   ; No autocompletion.
#ACO_AUTOSUGGEST        = $1  ; Enable the autosuggest drop-down list.
#ACO_AUTOAPPEND           = $2  ; Enable autoappend.
#ACO_SEARCH             = $4  ; Add a search item to the dropdown list of completed strings. If this is item is selected, you should launch a search engine to assist the user.
#ACO_FILTERPREFIXES      = $8  ; Don't match common prefixes, such as "www.", "http://", and so on.
#ACO_USETAB               = $10 ; Use the TAB key to select an item from the drop-down list. This flag is disabled by default.
#ACO_UPDOWNKEYDROPSLIST   = $20 ; Use the UP ARROW and DOWN ARROW keys to display the autosuggest drop-down list.
#ACO_RTLREADING           = $40 ; If ACO_RTLREADING is set, the text is read in the opposite direction from the text in the parent window.


; -----------------------------------------------

#Window_0 = 0
#String_0 = 0

Dim Strings.s(17)
Strings(0)  = "Else"
Strings(1)  = "ElseIf"
Strings(2)  = "EnableDebugger"
Strings(3)  = "EnableExplicit"
Strings(4)  = "End"
Strings(5)  = "EndDataSection"
Strings(6)  = "EndEnumeration"
Strings(7)  = "EndIf"
Strings(8)  = "EndImport"
Strings(9)  = "EndInterface"
Strings(10) = "EndMacro"
Strings(11) = "EndProcedure"
Strings(12) = "EndSelect"
Strings(13) = "EndStructure"
Strings(14) = "EndStructureUnion"
Strings(15) = "EndWith"
Strings(16) = "Enumeration"

; Initialize the COM environment
CoInitialize_(0)

If OpenWindow(#Window_0, 323, 219, 600, 300, "AutoComplete",  #PB_Window_SystemMenu | #PB_Window_TitleBar | #PB_Window_ScreenCentered )
  If CreateGadgetList(WindowID(#Window_0))
    StringGadget(#String_0, 40, 40, 320, 20, "")
    SetActiveGadget(#String_0)
   
    ; This creates the autocomplete object:
    ;
    If CoCreateInstance_(?CLSID_AutoComplete, 0, 1, ?IID_IAutoComplete2, @AutoComplete.IAutoComplete2) = #S_OK   
   
      ; Lets change the options for autocomplete a bit:
      ; See the constant definitions above for the available options.
      ; There is a dropdown or in-place suggestion mode.
      ;
      AutoComplete\SetOptions(#ACO_AUTOSUGGEST|#ACO_USETAB)     
   
      ; This creates the IEnumString interface from our string array:
      ;
      Enum.IEnumString = New_EnumString(Strings(), 18)
     
      ; This sets the autocomplet with our Enum object.
      ; If you want more info on the last 2 parameters, read here:
      ; http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/ifaces/iautocomplete/Init.asp
      ; Note that these must be unicode strings if present!
      ;
      ; This also works for Comboboxes, you just need to get the handle to the edit
      ; control that is contained inside with the GetComboBoxInfo_() function.     
      ;
      AutoComplete\Init(GadgetID(#String_0), Enum, 0, 0)             
     
      Repeat
      Until WaitWindowEvent() = #PB_Event_CloseWindow
     
      ; Release our own references to the objects to avoid a memory leak
      ;
      Enum\Release()       
      AutoComplete\Release()     
    EndIf
  EndIf
EndIf


CoUninitialize_()

DataSection


  CLSID_AutoComplete: ; {00BB2763-6A77-11D0-A535-00C04FD7D062}
    Data.l $00BB2763
    Data.w $6A77, $11D0
    Data.b $A5, $35, $00, $C0, $4F, $D7, $D0, $62

  IID_IAutoComplete: ; {00BB2762-6A77-11D0-A535-00C04FD7D062}
    Data.l $00BB2762
    Data.w $6A77, $11D0
    Data.b $A5, $35, $00, $C0, $4F, $D7, $D0, $62

  IID_IAutoComplete2: ; {EAC04BC0-3791-11D2-BB95-0060977B464C}
    Data.l $EAC04BC0
    Data.w $3791, $11D2
    Data.b $BB, $95, $00, $60, $97, $7B, $46, $4C

EndDataSection


Includefile for IEnumString:
Code:
;
;  Generic Implementation of an IEnumString Interface.
;  New_EnumString(StringArray(), ItemCount) will create a new IEnumString instance
;  with a referencecount of 1. (this means you must call IEnumString\Release()
;  to release this reference once you do not need it anymore.)
;
;
;


; To allow for the ::Clone() method without dublicating the buffer,
; the string buffer is kept separate, with its own reference count.
Structure EnumStringBuffer
  RefCount.l
  Strings.l[0]
  ; following is the literal string data
EndStructure

; IEnumString data
Structure EnumString
 *Vtbl
  RefCount.l
  StringCount.l
  Enumerator.l
 *Buffer.EnumStringBuffer
EndStructure


Procedure IEnumString_QueryInterface(*THIS.EnumString, *IID.IID, *Object.LONG)
  If *Object = 0
    ProcedureReturn #E_INVALIDARG
  ElseIf CompareMemory(*IID, ?IID_IUnknown, SizeOf(IID)) Or  CompareMemory(*IID, ?IID_IEnumString, SizeOf(IID))
    *Object\l = *THIS
    *THIS\RefCount + 1
    ProcedureReturn #S_OK
  Else
    *Object\l = 0
    ProcedureReturn #E_NOINTERFACE
  EndIf
EndProcedure


Procedure IEnumString_AddRef(*THIS.EnumString)
  *THIS\RefCount + 1
  ProcedureReturn *THIS\RefCount
EndProcedure


Procedure IEnumString_Release(*THIS.EnumString)
  *THIS\RefCount - 1
 
  If *THIS\RefCount = 0
    *THIS\Buffer\RefCount - 1
    If *THIS\Buffer\RefCount = 0
      FreeMemory(*THIS\Buffer)
    EndIf
   
    FreeMemory(*THIS)
    ProcedureReturn 0
  Else
    ProcedureReturn *THIS\RefCount
  EndIf
EndProcedure


Procedure IEnumString_Next(*THIS.EnumString, celt, *rgelt.LONG, *pceltFetched.LONG)
  If *THIS\Enumerator + celt <= *THIS\StringCount
    count = celt
  Else
    count = *THIS\StringCount - *THIS\Enumerator
  EndIf
 
  For i = 0 To count-1
    *rgelt\l = *THIS\Buffer\Strings[*THIS\Enumerator + i]
    *rgelt + 4
  Next i
 
  *THIS\Enumerator + count
 
  If *pceltFetcted
    *pceltFetched\l = count
  EndIf
 
  If count = celt
    ProcedureReturn #S_OK
  Else
    ProcedureReturn #S_FALSE
  EndIf
EndProcedure


Procedure IEnumString_Skip(*THIS.EnumString, celt)
  *THIS\Enumerator + celt
  If *THIS\Enumerator <= *THIS\StringCount
    ProcedureReturn #S_OK
  Else
    ProcedureReturn #S_FALSE
  EndIf
EndProcedure


Procedure IEnumString_Reset(*THIS.EnumString)
  *THIS\Enumerator = 0
  ProcedureReturn #S_OK
EndProcedure


Procedure IEnumString_Clone(*THIS.EnumString, *ppenum.LONG)
  If *ppenum = 0
    ProcedureReturn #E_INVALIDARG
  Else   
    *Clone.EnumString = AllocateMemory(SizeOf(EnumString))
    If *Clone
      CopyMemory(*THIS, *Clone, SizeOf(EnumString))
      *Clone\RefCount = 1
      *Clone\Buffer\RefCount + 1
      *ppenum\l = *Clone
      ProcedureReturn #S_OK
    Else
      *ppenum\l = 0
      ProcedureReturn #E_OUTOFMEMORY
    EndIf 
  EndIf
EndProcedure


Procedure New_EnumString(StringArray$(1), StringCount)
  *THIS.EnumString = 0

  Size = 4 + StringCount * 4
  For i = 0 To StringCount-1
    Size + Len(StringArray$(i)) * 2 + 2
  Next i

  *StringBuffer.EnumStringBuffer = AllocateMemory(Size)
  If *StringBuffer
    *StringBuffer\RefCount = 1
    *Pointer = *StringBuffer + 4 + StringCount * 4
   
    For i = 0 To StringCount - 1     
      *StringBuffer\Strings[i] = *Pointer 
      PokeS(*Pointer, StringArray$(i), -1, #PB_Unicode)
      *Pointer + Len(StringArray$(i)) * 2 + 2
    Next i
   
    *THIS = AllocateMemory(SizeOf(EnumString))
    If *THIS
      *THIS\Vtbl        = ?IEnumStringVtbl
      *THIS\RefCount    = 1
      *THIS\StringCount = StringCount
      *THIS\Enumerator  = 0
      *THIS\Buffer      = *StringBuffer
    Else
      FreeMemory(*StringBuffer)
    EndIf
  EndIf   

  ProcedureReturn *THIS
EndProcedure


DataSection

  IEnumStringVtbl:
    Data.l @IEnumString_QueryInterface()
    Data.l @IEnumString_AddRef()
    Data.l @IEnumString_Release()
    Data.l @IEnumString_Next()
    Data.l @IEnumString_Skip()
    Data.l @IEnumString_Reset()
    Data.l @IEnumString_Clone()
   
  IID_IUnknown: ; {00000000-0000-0000-C000-000000000046}
    Data.l $00000000
    Data.w $0000, $0000
    Data.b $C0, $00, $00, $00, $00, $00, $00, $46
   
  IID_IEnumString: ; {00000101-0000-0000-C000-000000000046}
    Data.l $00000101
    Data.w $0000, $0000
    Data.b $C0, $00, $00, $00, $00, $00, $00, $46

EndDataSection


[edit]
Oh well, just found a similar example by Justin: http://www.purebasic.fr/english/viewtopic.php?t=15627
Mine could still be usefull though, as the my IEnumString implementation
is more complete, with all methods implemented and all specified scenarios handled.
Its also simpler to use imho.

_________________
quidquid Latine dictum sit altum videtur


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Nov 22, 2006 5:39 am 
Offline
Enthusiast
Enthusiast
User avatar

Joined: Thu Oct 16, 2003 8:09 pm
Posts: 182
Very Useful. Thanks :)


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Jul 09, 2007 12:37 am 
Offline
PureBasic Expert
PureBasic Expert

Joined: Mon Jun 02, 2003 1:42 am
Posts: 2010
Location: Ashland, KY
Great stuff!

Question, though..

Code:
CoCreateInstance_(?CLSID_AutoComplete, 0, 1, ?IID_IAutoComplete2, @AutoComplete.IAutoComplete2)


What do the 0 and 1 correspond to? Gadget IDs?

Can a single object operate on multiple controls (with the same list of items to be autocompleted)?

_________________
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Jul 09, 2007 4:08 am 
Offline
PureBasic Bullfrog
PureBasic Bullfrog
User avatar

Joined: Wed Jul 06, 2005 5:42 am
Posts: 8006
Location: Fort Nelson, BC, Canada
For those parameters:

The second parameter is either #Null (as in this case) if the object is not being created as part of an aggregate, else it's a pointer to the aggregate object's IUnknown interface.

The third parameter is the execution context in which the object is to be run, in this case #CLSCTX_INPROC_SERVER = $1, which is part of the CLSCTX enumeration.

For multiple gadgets having the autocomplete enabled at once, I believe you'd need to create and configure a separate instance for each gadget you're using it on, though you shouldn't have any problem using the same string list for all if you so desired, ie New_EnumString() would only need to be called once.

_________________
Veni, vidi, vici.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Jul 09, 2007 12:10 pm 
Offline
PureBasic Team
PureBasic Team
User avatar

Joined: Fri Apr 25, 2003 5:21 pm
Posts: 5783
Location: Germany
netmaestro explained the parameters well.

You need a separate AutoComplete and Enumerator object for each gadget that uses autocomplete.
(the enumerator has an internal state which would be messed up if it is used by two gadgets)

You can however clone the enumerator:
Code:
Enum.IEnumString = New_EnumString(StringArray(), Count)

If Enum\Clone(@NewEnum.IEnumString) = #S_OK
  ; there are 2 objects now
EndIf


In my implementation, a cloned enumerator shares the stringbuffer with the original,
so you get two independant objects with very little extra memory usage.
(The stringbuffer is freed when the last enumerator that uses it is released)

_________________
quidquid Latine dictum sit altum videtur


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Jul 09, 2007 2:52 pm 
Offline
PureBasic Expert
PureBasic Expert

Joined: Mon Jun 02, 2003 1:42 am
Posts: 2010
Location: Ashland, KY
Excellent -- thanks!!

_________________
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Jul 12, 2007 11:00 pm 
Offline
PureBasic Expert
PureBasic Expert

Joined: Mon Jun 02, 2003 1:42 am
Posts: 2010
Location: Ashland, KY
I released a version of kBilling with this implemented and have received a number of crash reports on this line :

*StringBuffer.EnumStringBuffer = AllocateMemory(Size)

There have been too many reports for it to be an issue where someone is really running out of memory (the last person had 3 GB of memory).

I am calling this code multiple times, if that could cause any conflicts (though I'm not cloning).

I know that's not much to go on but would you have any ideas?

_________________
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Jul 12, 2007 11:10 pm 
Offline
PureBasic Expert
PureBasic Expert

Joined: Mon Jun 02, 2003 1:42 am
Posts: 2010
Location: Ashland, KY
Oh, I also tried to compile kBilling as threadsafe but backed that out when this problem arose. The problem seems to persist after I did that, though.

_________________
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Jul 12, 2007 11:19 pm 
Offline
PureBasic Team
PureBasic Team
User avatar

Joined: Fri Apr 25, 2003 5:21 pm
Posts: 5783
Location: Germany
Well, the only reason that comes to mind is if you call the function with a negative "StringCount"
value, in which case the 'Size' variabe could become negative.
Can you check this ?

Otherwise i do not really know what might cause a crash at this line. There is really
not much about AllocateMemory() itself that could cause a crash.

_________________
quidquid Latine dictum sit altum videtur


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Jul 12, 2007 11:27 pm 
Offline
PureBasic Expert
PureBasic Expert

Joined: Mon Jun 02, 2003 1:42 am
Posts: 2010
Location: Ashland, KY
Yea, that's what I was thinking -- I added a negative check to see.. Thanks!

_________________
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Jul 17, 2007 5:45 am 
Offline
PureBasic Expert
PureBasic Expert

Joined: Mon Jun 02, 2003 1:42 am
Posts: 2010
Location: Ashland, KY
Nope, that wasn't it. Dozens more reports with a crash at the
Code:
*StringBuffer.EnumStringBuffer = AllocateMemory(Size)
And
*THIS = AllocateMemory(SizeOf(EnumString))
..lines from :

Code:
Procedure New_EnumString(StringArray$(1), StringCount)
  *THIS.EnumString = 0
 
  Size = 4 + StringCount * 4
  For i = 0 To StringCount-1
    Size + Len(StringArray$(i)) * 2 + 2
  Next i
 
  *StringBuffer.EnumStringBuffer = AllocateMemory(Size)
  If *StringBuffer
    *StringBuffer\RefCount = 1
    *Pointer = *StringBuffer + 4 + StringCount * 4
   
    For i = 0 To StringCount - 1     
      *StringBuffer\Strings[i] = *Pointer
      PokeS(*Pointer, StringArray$(i), -1, #PB_Unicode)
      *Pointer + Len(StringArray$(i)) * 2 + 2
    Next i
   
    *THIS = AllocateMemory(SizeOf(EnumString))
    If *THIS
      *THIS\Vtbl        = ?IEnumStringVtbl
      *THIS\RefCount    = 1
      *THIS\StringCount = StringCount
      *THIS\Enumerator  = 0
      *THIS\Buffer      = *StringBuffer
    Else
      FreeMemory(*StringBuffer)
    EndIf
  EndIf   
 
  ProcedureReturn *THIS
EndProcedure


It's crashing before I have a chance to test the result. Sizes are all reporting positive.. Especially the SizeOf(EnumString) one -- that's a constant! The call itself is causing the crash I guess.

I've tried both a thread safe and non-thread safe but got the same results. I haven't tried a Unicode compile due to some userlib issues.

_________________
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Jul 17, 2007 4:42 pm 
Offline
PureBasic Team
PureBasic Team
User avatar

Joined: Fri Apr 25, 2003 5:21 pm
Posts: 5783
Location: Germany
The only way AllocateMemory() can crash is if the Memory Heap got invalid.
This happens when the heap control structures are overwritten by a wrong memory operation.
The problem is that these kinds of errors are very hard to trace, as the problem
can be somewhere totally else than the line at which it crashes.

I just fixed a bug in PokeS() which might be related, so please test with this new Memory lib first:
http://freak.purearea.net/v4/Memory

If this does not help, here is a small procedure/macro to test for Heap invalidation:
Code:
Procedure ValidatePBHeap(LineNumber)
  Protected Heap
  !mov eax, dword [_PB_MemoryBase]
  !mov [p.v_Heap], eax
  If HeapValidate_(Heap, 0, 0) = 0
    MessageRequester("Error", "PB Memory Heap invalid at Line: "+Str(LineNumber))
  EndIf 
EndProcedure

Macro _validate
  ValidatePBHeap(#PB_Compiler_Line)
EndMacro


Place the _validate macro before the AllocateMemory() lines to confirm if the heap
is indeed invalid. If so, try to place the _validate macro in various places to
narrow down the place that causes the invalidation.

Sorry for the inconvinience, but there is no easier way to track such an error.
If notning else helps, i could provide you with a special build of the debugger
that checks heap validation for every executed source line. (its quite show then though)

btw, which PB version do you compile this with exactly ?

_________________
quidquid Latine dictum sit altum videtur


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Jul 17, 2007 4:47 pm 
Offline
PureBasic Expert
PureBasic Expert

Joined: Mon Jun 02, 2003 1:42 am
Posts: 2010
Location: Ashland, KY
Thanks for the explanation! And no worries, I'll work to figure out what's going on.

PureBasic v4.02 is what I compiled with. I'll get the heap validation macros in there and see if I can get some users that were having trouble to test. The frustrating thing for me is that I can't reproduce the problem on any computer here (I've tried 6 different machines!).

I downloaded that memory lib. It went from 7k to up over 34k.. Was that expected?

_________________
-Mitchell
Check out kBilling for all your billing software needs!
http://www.k-billing.com
Code Signing / Authenticode Certificates (Get rid of those Unknown Publisher warnings!)
http://codesigning.ksoftware.net


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Jul 17, 2007 5:01 pm 
Offline
PureBasic Team
PureBasic Team
User avatar

Joined: Fri Apr 25, 2003 5:21 pm
Posts: 5783
Location: Germany
> I downloaded that memory lib. It went from 7k to up over 34k.. Was that expected?

I accidently build an uncompressed PB library.
It makes no difference for the created executable though. It only affects how the compiler loads the lib.

_________________
quidquid Latine dictum sit altum videtur


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Jul 17, 2007 5:59 pm 
Offline
Always Here
Always Here

Joined: Mon Sep 22, 2003 6:45 pm
Posts: 7439
Location: Norway
The first time I tested this I got an invalid memory access on the last line. But it was only once.

Edit:
Ok, with this code: (the example with the include file pasted at the top) http://pastebin.ca/623482
I get an invalid memory access at the last (empty) line of the file after I do this:
1. Run it
2. Type "else"
3. Enlarge the dropdown list (drag with mouse)
4. Press arrow down
5. Press enter
6. Press Alt+F4


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 71 posts ]  Go to page 1, 2, 3, 4, 5  Next

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye