Requires RegisterSyncCallback (for multi-threaded APIs)
Code: Select all
; ==================================================================================================================================
; Class-based version of a script by nepter
; -> autohotkey.com/board/topic/96129-ahk-l-custom-autocompletion-for-edit-control-with-drop-down-list/
; Namespaces:
; IAutoComplete
; IEnumString
; Requires:
; RegisterSyncCallback() -> autohotkey.com/boards/viewtopic.php?f=6&t=21223
; MSDN:
; IAutoComplete -> msdn.microsoft.com/en-us/library/bb776292(v=vs.85).aspx
; IAutoComplete2 -> msdn.microsoft.com/en-us/library/bb776288(v=vs.85).aspx
; IAutoCompleteDropDown -> msdn.microsoft.com/en-us/library/bb776286(v=vs.85).aspx
; AUTOCOMPLETEOPTIONS -> msdn.microsoft.com/en-us/library/bb762479(v=vs.85).aspx
; ==================================================================================================================================
; Creates a new autocompletion object (lib compatible).
; Parameters:
; HEDT - Handle to an edit control.
; Strings - Simple array of autocompletion strings. If you pass a non-object value the string table will be empty.
; Options - Simple array of autocomplete options (see SetOptions() method).
; Default: "" -> AUTOSUGGEST
; WantReturn - Set to True to pass the Return key to single-line edit controls to close the autosuggest drop-down list.
; Note: The edit control will be subclassed in this case.
; Default: False
; Enable - By default, autocompletion will be enabled after creation. Pass False to disable it.
; Return values:
; On success, the function returns a new instance of the AutoComplete class; otherwise an empty string.
; ==================================================================================================================================
IAutoComplete_Create(HEDT, Strings, Options := "", WantReturn := False, Enable := True) {
Return New IAutoComplete(HEDT, Strings, Options, WantReturn, Enable)
}
; ==================================================================================================================================
; Used internally to pass the return key to single-line edit controls.
; ==================================================================================================================================
IAutoComplete_SubclassProc(HWND, Msg, wParam, lParam, ID, Data) {
If (Msg = 0x0087) && (wParam = 13) ; WM_GETDLGCODE, VK_RETURN
Return 0x0004 ; DLGC_WANTALLKEYS
If (Msg = 0x0002) { ; WM_DESTROY
DllCall("RemoveWindowSubclass", "Ptr", HWND, "Ptr", Data, "Ptr", ID)
DllCall("GlobalFree", "Ptr", Data)
If (IAutoCompleteAC := Object(ID))
IAutoCompleteAC.SubclassProc := 0
}
Return DllCall("Comctl32.dll\DefSubclassProc", "Ptr", HWND, "UInt", Msg, "Ptr", wParam, "Ptr", lParam)
}
; ==================================================================================================================================
Class IAutoComplete {
Static Attached := []
; -------------------------------------------------------------------------------------------------------------------------------
; Constructor - see AutoComplete_Create()
; -------------------------------------------------------------------------------------------------------------------------------
__New(HEDT, Strings, Options := "", WantReturn := False, Enable := True) {
Static IAC2_Init := A_PtrSize * 3
If AutoComplete.Attached[HEDT]
Return ""
This.HWND := HEDT
This.SubclassProc := 0
If !(IAC2 := ComObjCreate("{00BB2763-6A77-11D0-A535-00C04FD7D062}", "{EAC04BC0-3791-11d2-BB95-0060977B464C}"))
Return ""
This.IAC2 := IAC2
If !(IES := IEnumString_Create())
Return ""
If !IEnumString_SetStrings(IES, Strings) {
DllCall("GlobalFree", "Ptr", IES)
Return ""
}
This.IES := IES
This.VTBL := NumGet(IAC2 + 0, "UPtr")
If DllCall(NumGet(This.VTBL + IAC2_Init, "UPtr"), "Ptr", IAC2 + 0, "Ptr", HEDT, "Ptr", IES, "Ptr", 0, "Ptr", 0, "UInt")
Return ""
This.SetOptions(Options = "" ? ["AUTOSUGGEST"] : Options)
This.Enabled := True
If !(Enable)
This.Disable()
If (WantReturn) {
ControlGet, Styles, Style, , , ahk_id %HEDT%
If !(Styles & 0x0004) && (CB := RegisterCallback("IAutoComplete_SubclassProc")) { ; !ES_MULTILINE
If DllCall("SetWindowSubclass", "Ptr", HEDT, "Ptr", CB, "Ptr", &This, "Ptr", CB, "UInt")
This.SubclassProc := CB
Else
DllCall("GlobalFree", "Ptr", CB, "Ptr")
}
}
AutoComplete.Attached[HEDT] := True
}
; -------------------------------------------------------------------------------------------------------------------------------
; Destructor
; -------------------------------------------------------------------------------------------------------------------------------
__Delete() {
; The edit control keeps own references to IAC2. Hence autocompletion has to be disabled before it can be released.
; The only way to reenable autocompletion is to assign a new autocompletion object to the edit.
If (This.IAC2) {
This.Disable()
ObjRelease(This.IAC2)
}
If (This.SubclassProc) {
DllCall("RemoveWindowSubclass", "Ptr", This.HWND, "Ptr", This.SubclassProc, "Ptr", &This)
DllCall("GlobalFree", "Ptr", This.SubclassProc)
}
AutoComplete.Attached.Delete(This.HWND)
}
; -------------------------------------------------------------------------------------------------------------------------------
; Enables / disables autocompletion.
; Enable - True or False
; -------------------------------------------------------------------------------------------------------------------------------
Enable(Enable := True) {
Static IAC2_Enable := A_PtrSize * 4
If !(This.VTBL)
Return False
This.Enabled := !!Enable
Return !DllCall(NumGet(This.VTBL + IAC2_Enable, "UPtr"), "Ptr", This.IAC2, "Int", !!Enable, "UInt")
}
; -------------------------------------------------------------------------------------------------------------------------------
; Disables autocompletion.
; -------------------------------------------------------------------------------------------------------------------------------
Disable() {
Return This.Enable(False)
}
; -------------------------------------------------------------------------------------------------------------------------------
; Sets the autocompletion options.
; Options - Simple array of option strings corresponding to the keys defined in ACO
; -------------------------------------------------------------------------------------------------------------------------------
SetOptions(Options) {
Static IAC2_SetOptions := A_PtrSize * 5
Static ACO := {NONE: 0, AUTOSUGGEST: 1, AUTOAPPEND: 2, SEARCH: 4, FILTERPREFIXES: 8, USETAB: 16
, UPDOWNKEYDROPSLIST: 32, RTLREADING: 64, WORD_FILTER: 128, NOPREFIXFILTERING: 256}
If !(This.VTBL)
Return False
Opts := 0
For Each, Opt In Options
Opts |= (Opt := ACO[Opt]) <> "" ? Opt : 0
Return !DllCall(NumGet(This.VTBL + IAC2_SetOptions, "UPtr"), "Ptr", This.IAC2, "UInt", Opts, "UInt")
}
; -------------------------------------------------------------------------------------------------------------------------------
; Updates the autocompletion strings.
; Strings - Simple array of strings. If you pass a non-object value the string table will be emptied.
; -------------------------------------------------------------------------------------------------------------------------------
UpdStrings(Strings) {
Static IID_IACDD := "{3CD141F4-3C6A-11d2-BCAA-00C04FD929DB}" ; IAutoCompleteDropDown
, IACDD_ResetEnumerator := A_PtrSize * 4
If !(This.IES)
Return False
If !(IEnumString_SetStrings(This.IES, Strings))
Return False
If (IACDD := ComObjQuery(This.IAC2, IID_IACDD)) {
DllCall(NumGet(NumGet(IACDD + 0, "UPtr") + IACDD_ResetEnumerator, "UPtr"), "Ptr", This.IAC2, "UInt")
ObjRelease(IACDD)
}
Return True
}
}
; ==================================================================================================================================
; IEnumString -> msdn.microsoft.com/en-us/library/ms687257(v=vs.85).aspx
; For internal use by AutoComplete only!!!
; ==================================================================================================================================
IEnumString_Create() {
Static IESInit := True, IESSize := A_PtrSize * 10, IESVTBL
If (IESInit) {
VarSetCapacity(IESVTBL, IESSize, 0)
Addr := &IESVTBL + A_PtrSize
Addr := NumPut(RegisterSyncCallback("IEnumString_QueryInterface") , Addr + 0, "UPtr")
Addr := NumPut(RegisterSyncCallback("IEnumString_AddRef") , Addr + 0, "UPtr")
Addr := NumPut(RegisterSyncCallback("IEnumString_Release") , Addr + 0, "UPtr")
Addr := NumPut(RegisterSyncCallback("IEnumString_Next") , Addr + 0, "UPtr")
Addr := NumPut(RegisterSyncCallback("IEnumString_Skip") , Addr + 0, "UPtr")
Addr := NumPut(RegisterSyncCallback("IEnumString_Reset") , Addr + 0, "UPtr")
Addr := NumPut(RegisterSyncCallback("IEnumString_Clone") , Addr + 0, "UPtr")
IESInit := False
}
If !(IES := DllCall("GlobalAlloc", "UInt", 0x40, "Ptr", IESSize, "UPtr"))
Return False
DllCall("RtlMoveMemory", "Ptr", IES, "Ptr", &IESVTBL, "Ptr", IESSize)
NumPut(IES + A_PtrSize, IES + 0, "UPtr")
Return IES
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_SetStrings(IES, ByRef Strings) {
PrevTbl := NumGet(IES + (A_PtrSize * 9), "UPtr")
StrSize := 0
StrArray := []
Loop, % Strings.Length()
If ((S := Strings[A_Index]) <> "")
L := StrPut(S, "UTF-16") * 2
, StrSize += L
, StrArray.Push({S: S, L: L})
Else
Break
StrCount := StrArray.Length()
StrTblSize := (A_PtrSize * 2) + (StrCount * A_PtrSize * 2) + StrSize
If !(StrTbl := DllCall("GlobalAlloc", "UInt", 0x40, "Ptr", StrTblSize, "UPtr"))
Return False
Addr := StrTbl + A_PtrSize
Addr := NumPut(StrCount, Addr + 0, "UPtr")
StrPtr := Addr + (StrCount * A_PtrSize * 2)
For Each, Str In StrArray {
Addr := NumPut(StrPtr, Addr + 0, "UPtr")
Addr := NumPut(Str.L, Addr + 0, "UPtr")
StrPut(Str.S, StrPtr, "UTF-16")
StrPtr += Str.L
}
If (PrevTbl)
DllCall("GlobalFree", "Ptr", PrevTbl)
NumPut(StrTbl, IES + (A_PtrSize * 9), "UPtr")
Return True
}
; ----------------------------------------------------------------------------------------------------------------------------------
; VTBL
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_QueryInterface(IES, RIID, ObjPtr) {
Static IID := "{00000101-0000-0000-C000-000000000046}", IID_IEnumString := 0
, Init := VarSetCapacity(IID_IEnumString, 16, 0) + DllCall("Ole32.dll\IIDFromString", "WStr", IID, "Ptr", &IID_IEnumString)
Critical
If DllCall("Ole32.dll\IsEqualGUID", "Ptr", RIID, "Ptr", &IID_IEnumString, "UInt") {
IEnumString_AddRef(IES)
Return !(NumPut(IES, ObjPtr + 0, "UPtr"))
}
Else
Return 0x80004002
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_AddRef(IES) {
NumPut(RefCount := NumGet(IES + (A_PtrSize * 8), "UPtr") + 1, IES + (A_PtrSize * 8), "UPtr")
Return RefCount
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Release(IES) {
RefCount := NumGet(IES + (A_PtrSize * 8), "UPtr")
If (RefCount > 0) {
NumPut(--RefCount, IES + (A_PtrSize * 8), "UPtr")
If (RefCount = 0) {
DllCall("GlobalFree", "Ptr", NumGet(IES + (A_PtrSize * 9), "UPtr")) ; string table
DllCall("GlobalFree", "Ptr", IES)
}
}
Return RefCount
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Next(IES, Fetch, Strings, Fetched) {
Critical
I := 0
, StrTbl := NumGet(IES + (A_PtrSize * 9), "UPtr")
, Current := NumGet(StrTbl + 0, "UPtr")
, Maximum := NumGet(StrTbl + A_PtrSize, "UPtr")
, StrAddr := StrTbl + (A_PtrSize * 2) + (A_PtrSize * Current * 2)
While (Current < Maximum) && (I < Fetch)
Ptr := NumGet(StrAddr + 0, "UPtr")
, Len := NumGet(StrAddr + A_PtrSize, "UPtr")
, Mem := DllCall("Ole32.dll\CoTaskMemAlloc", "Ptr", Len, "UPtr")
, DllCall("RtlMoveMemory", "Ptr", Mem, "Ptr", Ptr, "Ptr", Len)
, NumPut(Mem, Strings + (I * A_PtrSize), "Ptr")
, NumPut(++I, Fetched + 0, "UInt")
, NumPut(++Current, StrTbl + 0, "UPtr")
, StrAddr += A_PtrSize * 2
Return (I = Fetch) ? 0 : 1
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Skip(IES, Skip) {
Critical
StrTbl := NumGet(IES + (A_PtrSize * 9), "UPtr")
, Current := NumGet(StrTbl + 0, "UPtr")
, Maximum := NumGet(StrTbl + A_PtrSize, "UPtr")
If ((Current + Skip) <= Maximum)
Return (NumPut(Current + Skip, StrTbl, "UPtr") & 0)
Return 1
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Reset(IES) {
Return (NumPut(0, NumGet(IES + (A_PtrSize * 9), "UPtr"), "UPtr") & 0)
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Clone(IES, ObjPtr) { ; Not sure about the reference counter (IES + (A_PtrSize * 8))!
IESSize := DllCall("GlobalSize", "Ptr", IES, "Ptr")
StrTbl := NumGet(IES + (A_PtrSize * 9), "UPtr")
StrTblSize := DllCall("GlobalSize", "Ptr", StrTbl, "Ptr")
If !(IESClone := DllCall("GlobalAlloc", "UInt", 0x40, "Ptr", IESSize, "UPtr"))
Return False
If !(StrTblClone := DllCall("GlobalAlloc", "UInt", 0x40, "Ptr", StrTblSize, "UPtr")) {
DllCall("GlobalFree", "Ptr", IESClone)
Return False
}
DllCall("RtlMoveMemory", "Ptr", IESClone, "Ptr", IES, "Ptr", IESSize)
DllCall("RtlMoveMemory", "Ptr", StrTblClone, "Ptr", StrTbl, "Ptr", StrTblSize)
NumPut(0, IESClone + (A_PtrSize * 8), "UPtr") ; Set the reference counter to zero or one in this case???
NumPut(StrTblClone, IESCLone + (A_PtrSIze * 9), "UPtr")
Return (NumPut(IESClone, ObjPtr + 0, "UPtr") & 0)
}
; ==================================================================================================================================
Code: Select all
#NoEnv
#SingleInstance, Off
#NoTrayIcon
SetWorkingDir %A_ScriptDir%
#Include IAutoComplete.ahk
S := ["and"
, "array"
, "as"
, "asm"
, "begin"
, "case"
, "class"
, "const"
, "constructor"
, "destructor"
, "dispinterface"
, "div"
, "do"
, "downto"
, "else"
, "end"
, "except"
, "exports"
, "file"
, "finalization"
, "finally"
, "for"
, "function"
, "goto"
, "if"
, "implementation"
, "in"
, "inherited"
, "initialization"
, "inline"
, "interface"
, "is"
, "label"
, "library"
, "mod"
, "nil"
, "not"
, "object"
, "of"
, "or"
, "out"
, "packed"
, "procedure"
, "program"
, "property"
, "raise"
, "record"
, "repeat"
, "resourcestring"
, "set"
, "shl"
, "shr"
, "string"
, "then"
, "threadvar"
, "to"
, "try"
, "type"
, "unit"
, "until"
, "uses"
, "var"
, "while"
, "with"
, "xor"]
KeyWord := InputGui("Find a Keyword...", "`n Search for:", , S)
MsgBox, 0, Result, %KeyWord%
ExitApp
; Esc::ExitApp
InputGui(Title, Prompt, Default := "", Suggestions := "", Owner := "") {
If( Owner <> "" ) {
Gui %Owner%:+Disabled
Gui InputGui:+Owner%Owner%
}
Gui, InputGui:+AlwaysOnTop +LabelInputGui +LastFound -SysMenu +OwnDialogs
Gui, InputGui:Margin, 0, 0
Gui, InputGui:Color, White
Gui, InputGui:Font, s12 w400, Segoe UI
Gui, InputGui:Add, Text, x10 y10 w320 c002299, %Prompt%
Gui, InputGui:Font, s9 Normal
Gui, InputGui:Add, Edit, x10 y+30 w320 hwndhEdit, %Default%
Gui, InputGui:Add, Text, x0 y+30 w340 h30 -Background hwndhTxt
Gui, InputGui:Font, s12
Gui, InputGui:Add, Button, x120 yp+10 w100 gInputGuiOK Default, OK
Gui, InputGui:Add, Button, x+10 yp w100 gInputGuiClose hwndhBtn, Cancel
GuiControlGet, P, InputGui:Pos, %hBtn%
GuiControl, InputGui:Move, %hTxt%, % "h" . (PH + 20)
Gui, InputGui:Show, AutoSize, %Title%
If IsObject(Suggestions)
ACInput := IAutoComplete_Create(hEdit, Suggestions, ["AutoSuggest", "UseTab"])
WinWaitClose
Return Result
; ------------------------------------------------------------------------------
InputGuiOK:
GuiControlGet, Result, , %hEdit%
; InputGuiEscape:
InputGuiClose:
If(Owner <> "")
Gui %Owner%:-Disabled
Gui, Destroy
Return
}
/*
RegisterSyncCallback
A replacement for RegisterCallback for use with APIs that will call
the callback on the wrong thread. Synchronizes with the script's main
thread via a window message.
This version tries to emulate RegisterCallback as much as possible
without using RegisterCallback, so shares most of its limitations,
and some enhancements that could be made are not.
Other differences from v1 RegisterCallback:
- Variadic mode can't be emulated exactly, so is not supported.
- A_EventInfo can't be set in v1, so is not supported.
- Fast mode is not supported (the option is ignored).
- ByRef parameters are allowed (but ByRef is ignored).
- Throws instead of returning "" on failure.
*/
RegisterSyncCallback(FunctionName, Options:="", ParamCount:="")
{
if !(fn := Func(FunctionName)) || fn.IsBuiltIn
throw Exception("Bad function", -1, FunctionName)
if (ParamCount == "")
ParamCount := fn.MinParams
if (ParamCount > fn.MaxParams && !fn.IsVariadic || ParamCount+0 < fn.MinParams)
throw Exception("Bad param count", -1, ParamCount)
static sHwnd := 0, sMsg, sSendMessageW
if !sHwnd
{
Gui RegisterSyncCallback: +Parent%A_ScriptHwnd% +hwndsHwnd
OnMessage(sMsg := 0x8000, Func("RegisterSyncCallback_Msg"))
sSendMessageW := DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32.dll", "ptr"), "astr", "SendMessageW", "ptr")
}
if !(pcb := DllCall("GlobalAlloc", "uint", 0, "ptr", 96, "ptr"))
throw
DllCall("VirtualProtect", "ptr", pcb, "ptr", 96, "uint", 0x40, "uint*", 0)
p := pcb
if (A_PtrSize = 8)
{
/*
48 89 4c 24 08 ; mov [rsp+8], rcx
48 89 54'24 10 ; mov [rsp+16], rdx
4c 89 44 24 18 ; mov [rsp+24], r8
4c'89 4c 24 20 ; mov [rsp+32], r9
48 83 ec 28' ; sub rsp, 40
4c 8d 44 24 30 ; lea r8, [rsp+48] (arg 3, ¶ms)
49 b9 .. ; mov r9, .. (arg 4, operand to follow)
*/
p := NumPut(0x54894808244c8948, p+0)
p := NumPut(0x4c182444894c1024, p+0)
p := NumPut(0x28ec834820244c89, p+0)
p := NumPut( 0xb9493024448d4c, p+0) - 1
lParamPtr := p, p += 8
p := NumPut(0xba, p+0, "char") ; mov edx, nmsg
p := NumPut(sMsg, p+0, "int")
p := NumPut(0xb9, p+0, "char") ; mov ecx, hwnd
p := NumPut(sHwnd, p+0, "int")
p := NumPut(0xb848, p+0, "short") ; mov rax, SendMessageW
p := NumPut(sSendMessageW, p+0)
/*
ff d0 ; call rax
48 83 c4 28 ; add rsp, 40
c3 ; ret
*/
p := NumPut(0x00c328c48348d0ff, p+0)
}
else ;(A_PtrSize = 4)
{
p := NumPut(0x68, p+0, "char") ; push ... (lParam data)
lParamPtr := p, p += 4
p := NumPut(0x0824448d, p+0, "int") ; lea eax, [esp+8]
p := NumPut(0x50, p+0, "char") ; push eax
p := NumPut(0x68, p+0, "char") ; push nmsg
p := NumPut(sMsg, p+0, "int")
p := NumPut(0x68, p+0, "char") ; push hwnd
p := NumPut(sHwnd, p+0, "int")
p := NumPut(0xb8, p+0, "char") ; mov eax, &SendMessageW
p := NumPut(sSendMessageW, p+0, "int")
p := NumPut(0xd0ff, p+0, "short") ; call eax
p := NumPut(0xc2, p+0, "char") ; ret argsize
p := NumPut((InStr(Options, "C") ? 0 : ParamCount*4), p+0, "short")
}
NumPut(p, lParamPtr+0) ; To be passed as lParam.
p := NumPut(&fn, p+0)
p := NumPut(ParamCount, p+0, "int")
return pcb
}
RegisterSyncCallback_Msg(wParam, lParam)
{
if (A_Gui != "RegisterSyncCallback")
return
fn := Object(NumGet(lParam + 0))
paramCount := NumGet(lParam + A_PtrSize, "int")
params := []
Loop % paramCount
params.Push(NumGet(wParam + A_PtrSize * (A_Index-1)))
return %fn%(params*)
}
@lexikos: Would you please look at IEnumString_Clone()? Which value should the reference counter be set to in this case?