I wrote about autocompletion for TEdits before here and here.
Back then I was using the SHAutoComplete API function in the Shlwapi.dll because I am a lazy basterd™. That hasn’t changed really but I also love fiddling with stuff, so some years ago, I actually added directory and general string completion to dzlib (and apparently didn’t blog about it), this time using the IAutoComplete2 interface as described in this answer on StackOverflow, which I mentioned before.
Today I revisited that code and and added file and directory completion to it, in a way that also allows filtering the files with a given file mask. So the user can easily enter directories and eventually select a file:
To add this functionality to a TEdit control, one only needs to add the unit u_dzAutoCompleteFiles to the form and call TEdit_ActivateAutoCompleteFiles in the form’s constructor like this:
constructor Tf_AutoCompleteTest.Create(_Owner: TComponent); begin inherited; TEdit_ActivateAutoCompleteFiles(ed_AutoCompleteFiles, '*.dfm'); end;
The magic happens in a helper class TEnumStringFiles derived from the same TEnumStringAbstract class as TEnumStringDirs and TEnumStringStringList, implementing the IEnumString interface. It implements three methods:
- function Next(celt: LongInt; out elt; pceltFetched: PLongInt): HResult;
- function Reset: HResult
- function Skip(celt:LongInt): HResult;
Reset is called whenever Windows thinks it needs to get the autocompletion list. Basically that’s whenever the user presses the backslash key, but apparently there are also other events that trigger it. After that the functions Next and possibly Skip are called to get the actual list.
Since this class is supposed to return directories and files, it uses two instances of TSimpleDirectoryEnum which encapsulates SysUtils.FindFirst and SysUtils.FindNext. One is for directories the other is for files matching a given mask (actually “mask” is not quite the right word as the “mask” is always appended to the base string the user has entered).
Here is the complete unit (but you should really get the code from Sourceforge to make sure the get the latest version):
unit u_dzAutoCompleteFiles; interface uses Windows, Messages, SysUtils, Classes, StdCtrls, ActiveX, u_dzfileutils, i_dzAutoComplete; type TOnGetBaseFileEvent = procedure(_Sender: TObject; out _Base: string) of object; type ///<summary> /// Implementaition of IEnumString for files </summary> TEnumStringFiles = class(TEnumStringAbstract, IEnumString) private FOnGetBase: TOnGetBaseFileEvent; FDirEnum: TSimpleDirEnumerator; FFileEnum: TSimpleDirEnumerator; FBase: string; FFilter: string; procedure doOnGetBase(out _Base: string); function FindNextDirOrFile(out _DirOrFilename: WideString): Boolean; protected // IEnumString function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; override; function Skip(celt: Longint): HResult; override; function Reset: HResult; override; public constructor Create(_OnGetBase: TOnGetBaseFileEvent; const _Filter: string); destructor Destroy; override; end; procedure TEdit_ActivateAutoCompleteFiles(_ed: TCustomEdit; const _Filter: string); implementation { TEnumStringFiles } constructor TEnumStringFiles.Create(_OnGetBase: TOnGetBaseFileEvent; const _Filter: string); begin inherited Create; FFilter := _Filter; FOnGetBase := _OnGetBase; end; destructor TEnumStringFiles.Destroy; begin FreeAndNil(FDirEnum); FreeAndNil(FFileEnum); inherited; end; procedure TEnumStringFiles.doOnGetBase(out _Base: string); begin if Assigned(FOnGetBase) then FOnGetBase(Self, _Base); end; function TEnumStringFiles.FindNextDirOrFile(out _DirOrFilename: WideString): Boolean; var fn: string; begin if Assigned(FDirEnum) then begin Result := FDirEnum.FindNext(fn, True); if Result then begin _DirOrFilename := fn; Exit; //==> end; end; if Assigned(FFileEnum) then begin Result := FFileEnum.FindNext(fn, True); if Result then begin _DirOrFilename := fn; Exit; //==> end; end; Result := False; end; function TEnumStringFiles.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; var i: Integer; wStr: WideString; begin i := 0; while (i < celt) and FindNextDirOrFile(wStr) do begin TPointerList(elt)[i] := Pointer(wStr); Pointer(wStr) := nil; Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; function TEnumStringFiles.Reset: HResult; begin doOnGetBase(FBase); FreeAndNil(FDirEnum); FreeAndNil(FFileEnum); FDirEnum := TSimpleDirEnumerator.CreateForDirsOnly(FBase + '*'); FFileEnum := TSimpleDirEnumerator.CreateForFilesOnly(FBase + FFilter); Result := S_OK; end; function TEnumStringFiles.Skip(celt: Integer): HResult; var i: Integer; wStr: WideString; begin i := 0; while FindNextDirOrFile(wStr) do begin Inc(i); if i < celt then begin Result := S_OK; Exit; //==> end; end; Result := S_FALSE; end; type TAutoCompleteHelperFiles = class(TAutoCompleteHelper) private FFilter: string; procedure HandleOnGetBase(_Sender: TObject; out _Base: string); protected function CreateEnumStringInt: IEnumString; override; public constructor Create(_ed: TCustomEdit; const _Filter: string); end; procedure TEdit_ActivateAutoCompleteFiles(_ed: TCustomEdit; const _Filter: string); begin TAutoCompleteHelperFiles.Create(_ed, _Filter); end; { TAutoCompleteHelperFiles } constructor TAutoCompleteHelperFiles.Create(_ed: TCustomEdit; const _Filter: string); begin FFilter := _Filter; inherited Create(_ed); end; function TAutoCompleteHelperFiles.CreateEnumStringInt: IEnumString; begin Result := TEnumStringFiles.Create(HandleOnGetBase, FFilter); end; procedure TAutoCompleteHelperFiles.HandleOnGetBase(_Sender: TObject; out _Base: string); begin _Base := (FCtrl as TCustomEdit).Text; end; end.
The code is in the newly added u_dzAutoCompleteFiles unit in my dzlib.
If you would like to comment on this, go to this post in the international Delphi Praxis forum.