dummzeuch

On (re-)installing Delphi without active maintenance

 Delphi  Comments Off on On (re-)installing Delphi without active maintenance
Jun 102019
 

Apparently Embarcadero has decided to piss off the remaining Delphi users in the whole world, just to line their pockets. One of the annoyances of Delphi after Delphi 7 has been the enforced online activation. First, there was a 4 weeks grace period until that activation was necessary, but even that grace period has been gone lately. Also, there is a (not documented) limited number of allowed activations. Once you hit that limit, you had to contact Embarcadero to get a “bump”. Apparently that’s a manual process.

Now they have gone a step further:

You won’t get that “bump” unless you have got an active update subscription (which costs several hundred Euros per year).

Let me repeat that: You can’t install your legally paid for perpetual license of Delphi any more, unless pay a yearly fee of several hundred Euros.

Embarcadero’s GM (General Manager?) Atanas Popov wrote this on the topic:

Registration Limits

We have noticed compliance issues and increased Support efforts related to registration limit increases for customers on older product versions, who are no longer on Update Subscription. It is a standard industry practice to provide support to the most recent versions and to customers who have extended maintenance. We updated our processes and now route all issues raised from users who are not on Update Subscription to Sales and Renewals. We realize this is a change to previous operations and to reduce the impact to development projects, we issued a one-time registration limit increase for all customers who are close to hitting their registration count limit. This should address issues with re-installs of your licensed software on existing or new machines. Further, we will continue to look for options to make this more seamless through automation.
— Atanas Popov in his blog post From the GM: New Updates and Changes to the Registration “Bumps” Policy

I hope the part where he says “Further, we will continue to look for options to make this more seamless through automation.” he is not just trying to stall until the shit storm goes by, because, it won’t.

Just to make this clear:

It is a standard industry practice to provide support to the most recent versions and to customers who have extended maintenance.

It might be a standard industry practice, but in Germany, and I think in most other countries, this is illegal. You sold a perpetual license. So if you restrict your customers from using it, they are not getting the contractually guaranteed service. And you will probably find that some of them do employ lawyers.

At work, we have got 3 Delphi licenses: Two for Delphi 2007 and XE2 and another one (mine) on subscription. I have never used any of the support requests, I keep that subscription only be up to date on the features of the latest releases (and incidentally work on GExperts and other open source projects; with the consent of my employer of course). The other licenses used by two of my coworkers stay at the versions they are now because so far, I found nothing in later versions that would have made me want to update.

We currently are purely a Delphi shop, that is, all software development is done in Delphi. Most of these programs are for internal use only, with some very few exceptions. We do not sell software, we do road condition surveys all over Germany and Europe.

But if we cannot rely on Embarcadero delivering what they contractually agreed to deliver, we will not upgrade and pay for subscription, but probably look elsewhere. Not just because of the cost but also because it has become more difficult to find Delphi developers (Not just good ones. It has become nearly impossible to find any software developer who is willing to use Delphi at all.). I guess by pissing off Delphi developers world wide, Embarcadero will not increase their numbers, so this situation will not improve.

 Posted by on 2019-06-10 at 12:48

Add “Open with” for any executable to the Explorer context menu

 Windows  Comments Off on Add “Open with” for any executable to the Explorer context menu
Jun 102019
 

Many tools optionally add an “Open with [Name of tool]” entry to the context menu of the Windows Explorer. Some others don’t, even though it would be useful. Here is how to do it yourself:

  1. Open any text editor (Notepad will do)
  2. Copy and paste the following text into it:
    Windows Registry Editor Version 5.00
    
    [HKEY_CLASSES_ROOT\*\shell\Open with [DisplayName]\command]
    @="[Path\\to\\executable] %1"
    
  3. Replace [DisplayName] with the name of the tool.
  4. Replace [Path\\to\\executable] with the full path of the executable to open. Not that you must replace all backslashes with a dual backslash for this to work.
  5. Save the file as “Add-Open-with-MyTool-to-Context-Menu.reg”. Make sure that it is saved as a .reg file, not as a .txt file! (One of the annoyances of Notepad.)
  6. Open the created file with a double click and let the Registry Editor add it to the Registry

Examples:

Open with (portable) Notepad++

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\Open with Notepad++\command]
@="C:\\PortableApps\\Notepad++Portable\\Notepad++Portable.exe %1"

Notepad++ is a free and powerful replacement for Notepad and can be downloaded from notepad-plus-plus.org.

Open with (portable) HxD

Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\*\shell\Open with HxD\command]
@="C:\\PortableApps\\HxD\\HxD64.exe %1"

HxD is a free and powerful hex editor and can be downloaded from https://mh-nexus.de/en/hxd/.

Created entries

The created entries look like this:

 Posted by on 2019-06-10 at 11:53

GExperts adds copy and paste for Delphi Tool menu entries

 Delphi, GExperts  Comments Off on GExperts adds copy and paste for Delphi Tool menu entries
Jun 092019
 

In my last post I wrote about the export and import feature for custom Tools menu entries that GExperts adds to the Delphi IDE. I also mentioned that I was thinking about adding a custom clipboard format for copying and pasting these entries between multiple Delphi instances / versions.
OK, I did that. GExperts now also adds a popup menu to the Tool Properties dialog with two entries:

  • Copy entry to clipboard
  • Paste entry from clipboard

These entries use a custom clipboard format registered with Windows as "Delphi.ToolsEntry". It’s a binary format with the following structure:

  • Size: UInt32 -> the size of the structure
  • Title: array of WideChar, zero terminated
  • Path: array of WideChar, zero terminated
  • WorkingDir: array of WideChar, zero terminated
  • Params: array of WideChar, zero terminated

With this popup menu it is now possible to copy and paste Tools menu entries between multiple instances of the Delphi IDE. This works also between different Delphi versions, so you can copy the entries from Delphi 10.3 to Delphi 6 and vice versa.

There is no new GExperts release for now (I am waiting for the next Delphi point release which should be just around the corner. I’m just guessing, I have no internal knowledge about the release schedule.). In the meantime, if you’d like to get this feature, you will have to compile your own GExperts DLL.

If you would like to comment on this, go to this post in the international Delphi Praxis forum.

 Posted by on 2019-06-09 at 18:23

New GExperts IDE enhancement: Export and Import entries for the Tools menu

 Delphi, GExperts  Comments Off on New GExperts IDE enhancement: Export and Import entries for the Tools menu
Jun 082019
 

The Delphi IDE has the quite useful option to add custom entries to the Tools menu. These entries call external programs with some “Macros” that reference the current IDE status, e.g. the currently active project or source file and some of their properties like the output directory or the current editor column and row.

GExperts already enhances the Tools Properties dialog by adding auto completion for the file name and the working directory (for Delphi 6 and 7 it also adds support for Drag and Drop, but that doesn’t work for later versions).

It has always irked me that there was no easy way to port these custom tools entries from one Delphi version or installation to another. I always had to copy and paste four fields to achieve that.

GExperts now adds two new buttons to export and import the current entry:

These buttons write / read Delphi Tool Menu Entry files (*.dtme), which are in INI file format an look like this:

[Delphi Tool Menu Entry]
Title=Explore &Source Directory
Path=explorer.exe
WorkingDir=
Params=/e,/select, $EDNAME

So, to move an entry from one IDE version to another, export it from the first one, add a new entry to the second one and import the exported file.

I’m thinking about adding a special clipboard entry format for this so creating a file to copy these entries will no longer be necessary, but that’s for some future time (which may be very near but maybe not).
Edit: Done, see here.

I am also currently working on a small side project called Delphi Tools Manager which will supply that functionality outside the IDE, but it’s not quite ready for prime time yet. I actually already made a release available but had to withdraw it because I found that there are apparently two different ways the IDE stores these entries in the registry.

There is no new GExperts release for now (I am waiting for the next Delphi point release which should be just around the corner. I’m just guessing, I have no internal knowledge about the release schedule.). In the meantime, if you’d like to get this feature, you will have to compile your own GExperts DLL. But that’s far from being rocket science.

If you would like to comment on this, go to this post in the international Delphi Praxis forum.

 Posted by on 2019-06-08 at 18:45

Fix for Access Violation in u_dzAutocompleteStrings

 Delphi  Comments Off on Fix for Access Violation in u_dzAutocompleteStrings
Jun 022019
 

A while ago I blogged about the various possibilities for auto completion in TEdits. I created several units that supply auto completion based on

  • a StringList
  • files
  • directories
  • path (basically also directories)

They all followed a basic principle: Implement IAutoComplete and IAutoComplete2 and provide different implementations for the IEnumStrings interface. They worked fine in Delphi 2007 but I never tested them in later versions.

Today I wanted to add auto completion for files to a tool written in Delphi 10.2 and got an Access Violation in my implementation of the IEnumStrings.Next method. I could not figure out the problem, so I wrote a test program using the simplest implementation I had, the one using a StringList, to be sure there wasn’t any other part of the tool that wreaked havoc with the auto completion. The bug was still there and I still could not figure out what the problem was:

function TEnumStringStringList.Next(celt: Integer; out elt;
  pceltFetched: PLongint): HResult;
var
  i: Integer;
  wStr: WideString;
begin
  i := 0;
  while (i < celt) and (FCurrIndex < FStrings.Count) do begin
    wStr := FStrings[FCurrIndex];
    TPointerList(elt)[I] := Pointer(wStr); // <= AV here
    Pointer(wStr) := nil;
    Inc(i);
    Inc(FCurrIndex);
  end;
  if pceltFetched <> nil then
    pceltFetched^ := i;
  if i = celt then
    Result := S_OK
  else
    Result := S_FALSE;
end;

The Access violation happens in the line that assigns something to the first item in the elt TPointerList. As I said: It works fine in Delphi 2007.

Googling turned up a slightly different implementation for this:

    TPointerList(elt)[i] := CoTaskMemAlloc(2 * (Length(wStr) + 1));
    StringToWideChar(wStr, TPointerList(elt)[i], 2 * (Length(wStr) + 1));

Which unfortunately still caused an access violation. Finally I happened about this answer from Remy Lebau on StackOverflow that contained a type defintion with a comment:

type
  TPointerList = array[0..0] of Pointer; //avoid bug of Classes.pas declaration TPointerList = array of Pointer;

Guess what? It solved the problem. The code now works in both, Delphi 2007 and 10.2.

So, what causes the Access Violation? And why not in Delphi 2007?

The declaration of TPointerList in Delphi 2007 looks like this:

  TPointerList = array[0..MaxListSize - 1] of Pointer;

while the one in Delphi 10.2 is this:

  TPointerList = array of Pointer;

The first one is a static array the second one is a dynamic array. A Static array is a block of memory which is allocated automatically by the compiler for the given size, while a dynamic array is a pointer to an array descriptor (initially it’s NIL). Once the dynamic array gets initialized using the SetLength procedure the array descriptor contains the size of the array and enough memory to store the entries. The dynamic array variable points to the first entry.

So the difference between typecasting the elt parameter to a static array of pointers or an dynamic array of pointers causes the Access Violation. The change happened between Delphi XE and Delphi XE2. (I reported this as a bug in the Delphi RTL: RSP-24616. because apparently nobody had.).

elt is declared as an untyped out parameter. That means it is a pointer to some memory that the caller provides and which is to be written to by the Next method.

Typecasting it to a static array of pointers means that we assume that elt points to some memory which is to contain pointers to OleStrings. Typecasting it to a dynamic array of pointers means that we assume that elt points to a pointer to some memory that is to contain pointers, so the first (usually uninitialized) pointer in the array is referenced and since it points to somewhere which should not be written to, writing to it causes an access violation.

Adding the type definition given above fixed the problem.

The difference is the same as between a PInteger and a PPInteger Parameter:

type
  PInteger = ^Integer;
  PPInteger = ^PInteger;

procedure bla(param: PInteger);
begin
  param^ := 5;
end;

proceduer blub(param: PPInteger);
begin
  Param^^ := 5;
end;

var
  InvValue: integer;
begin
  bla(@IntValue);
  blub(@IntValue);
end;

If no type checking took place passing a pointer to an Integer to blub would compile but cause an Access Violation because it tries to use the value of IntValue as a pointer. Since it usually isn’t, the memory that “pointer” references would not be accessible.

Later I found another post on StackOverflow that actually was about that very Access Violation I was trying to find with an answer from Rudy Velthuis.

 Posted by on 2019-06-02 at 17:15

The 3 different License Types for Delphi

 Delphi  Comments Off on The 3 different License Types for Delphi
May 242019
 

I blogged about the Embarcadero License Center (ELC) and how to use it remotely. But I never went to the trouble of explaining the different kinds of license types that are available for Delphi (after all: I don’t sell these licenses, so why bother?). But somebody else just did: CodePartners blogged about Network Licensing in RAD Studio. It gives a pretty good overview.

I for one can definitely recommend switching from Named User to Network Named User licenses if you often need to install Delphi on a new computer (I need that rather often) or have a significant turnover of developers over the years.

Network licensing is supported in Delphi 2007 and later.

 Posted by on 2019-05-24 at 16:51

Installing dotNet 2.0 on Windows 10

 Windows, Windows 10  Comments Off on Installing dotNet 2.0 on Windows 10
May 232019
 

In theory it is simple to install the dotNet 2.0 framework on Windows 10: Just go to “Programs and Features”, select “Turn Windows Features on or off”, set the checkmark for “.NET Framework 3.5 (includes .NET 2.0 and 3.0)”, press OK and let Windows download the necessary files from Windows Update.

Unfortunately this only works most of the time. If you are unlucky like me and it doesn’t, you will start an odyssey of downloading installers from Microsoft (which also fail, because they try to download files from Windows Update for whatever reason), using the dism tool and possibly Power Shell to install it offline (both of which failed too in my case) and then either despair or find a reference to the “Missed Features Installer”.

When I arrived there, I was very suspicious (and so should you!) of downloading and using such a 3rd party installer. I used the download from Computer Bild not because I think they are the most brilliant computer magazine in Germany (they are not) but at least I trust them not to distribute malware (which is more than I trust the computer magazine CHIP). In addition, I used Virus Total to scan the installer. It gave me a thumbs up, so I was brave enough to run it.

Guess what? It worked. I now have a working .NET 3.5 and 2.0 framework on my computer and could finally install the program I actually wanted to install: The AVT Universal Package for accessing a camera.

 Posted by on 2019-05-23 at 18:29

Blocking the Windows Screen Saver in Delphi

 Delphi, Windows, Windows 10, Windows 7, Windows 8.1  Comments Off on Blocking the Windows Screen Saver in Delphi
May 222019
 

Sometimes your program needs to block the screen saver from automatically kicking in. My use case was that the program was recording data and whenever the screen saver was active, the data was lost (No idea why, it probably had something to do with the way HID is implemented in Windows.)
So I was looking for a way to fix that without forcing the user to turn off the screen saver. The methods that used to work under Windows XP no longer work in Windows 7 and later (I don’t care about Vista), so I googled and found this question on StackOverflow. The Windows API functions PowerCreateRequest + PowerSetRequest mentioned in the highest voted answer looked promising. Unfortunately they don’t seem bo be available in Delphi (Delphi 2007, which I used for that project, is too old to know them, but I coudn’t find them in Delphi 10.3 either). The first task was therefore to get a function declaration for Delphi. Google didn’t help here which meant that I had do create them myself. Not a big deal:

type
  TPowerCreateRequest = function(_Context: PReasonContext): THandle; stdcall;
  TPowerSetRequest = function(_Handle: THandle; _RequestType: TPowerRequestType): LongBool; stdcall;
  TPowerClearRequest = function(_Handle: THandle; _RequestType: TPowerRequestType): LongBool; stdcall;

I prefer loading such functions at runtime rather than the program not starting because some external reference is not avaiable. These functions are exported by kernel32.dll.

  FDllHandle := SafeLoadLibrary(kernel32);
  PowerCreateRequest := GetProcAddress(FDllHandle, 'PowerCreateRequest');
  PowerSetRequest := GetProcAddress(FDllHandle, 'PowerSetRequest');
  PowerClearRequest := GetProcAddress(FDllHandle, 'PowerClearRequest');
  if not Assigned(PowerCreateRequest) or not Assigned(PowerSetRequest) or not Assigned(PowerClearRequest) then
    raise EOsFunc.Create(_('Could not initialize the PowerXxxxRequest functions from kernel32.'));

Usage is not without its own problems. First, I had to declare the constants and parameters:

const
  POWER_REQUEST_CONTEXT_VERSION = 0;
  POWER_REQUEST_CONTEXT_DETAILED_STRING = 2;
  POWER_REQUEST_CONTEXT_SIMPLE_STRING = 1;
type
  PReasonContext = ^TReasonContext;
  TReasonContext = record
    Version: ULONG;
    Flags: DWORD;
    case Boolean of
      False: (
        SimpleReasonString: PWideChar;
        );
      True: (
        Detailed: record
          LocalizedReasonModule: HMODULE;
          LocalizedReasonId: ULONG;
          ReasonStringCount: ULONG;
          ReasonStrings: PPWideChar;
        end;
        );
  end;
type
  TPowerRequestType = (
    PowerRequestDisplayRequired = 0,
    PowerRequestSystemRequired = 1,
    PowerRequestAwayModeRequired = 2,
    PowerRequestExecutionRequired = 3);

Now, how do these functions work?

The first thing to do is creating a power request with PowerCreateRequest. This function requires a PReasonContext pointer which must be initialized correctly. The Version and Flags fields are simple: Assign one of the POWER_REQUEST_CONTEXT_xxx constants declared above. But what aobut the other fields? I decided to go with the simple case, that is: Set Flags to POWER_REQUEST_CONTEXT_SIMPLE_STRING and provide a value for SimpleReasonString.

var
  FRequestHandle: THandle;
  FContext: TReasonContext;
  FReason: array[0..255] of WideChar;
  // [...]
  FContext.Version := POWER_REQUEST_CONTEXT_VERSION;
  FContext.Flags := POWER_REQUEST_CONTEXT_SIMPLE_STRING;
  FContext.SimpleReasonString := @FReason;
  FRequestHandle := PowerCreateRequest(@FContext);
  if FRequestHandle = INVALID_HANDLE_VALUE then
    RaiseLastOSError;

Where FReason is an array of WideChar. My tests showed that the TReasonContext record and the reason string it points to must be available through the lifetime of the reason context. If it isn’t, the reason displayed by the powercfg tool (see below) will be corrupted. Therefore I did not use a WideString but a static array.

After the power request has been created, calls to PowerSetRequest and PowerClearRequest are possible.

  Win32Check(PowerSetRequest(FRequestHandle, PowerRequestDisplayRequired));

This call prevents the screen saver from starting automatically. A call to PowerClearRequest supposedly turns that off again (but I haven’t tested it).

I mentionend the powercfg tool above. It’s a Windows command line tool that among other functionality can display processes that have active power requests. e.g.

powercfg /requests
DISPLAY:
[PROCESS] \Device\HarddiskVolume2\Source\dzlib\tests\BlockScreenSaver\BlockScreenSaver.exe
test

SYSTEM:
None.

AWAYMODE:
None.

EXECUTION:
None.

PERFBOOST:
None.

The string “test” is the reason I passed to PowerCreateRequests.

I mentioned that failing to preserver the reason string results in a corrupted message in the display. It looked like this:

powercfg /requests
DISPLAY:
[PROCESS] \Device\HarddiskVolume2\Source\dzlib\tests\BlockScreenSaver\BlockScreenSaver.exe
?a?E?I???↑?E?↑?E?↑?E?↑?E?↑

Note that this tool requires administrator privileges (but power requests don’t).

I have added this code to my dzlib. It’s in u_dzOsUtils. There is also a simple test / demo program BlockScreenSaver.

If you would like to comment on this, go to this post in the international Delphi Praxis forum.

 Posted by on 2019-05-22 at 14:15

Autocompletion for TEdits revisited

 Delphi, dzLib  Comments Off on Autocompletion for TEdits revisited
Apr 282019
 

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 OSDN 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.

 Posted by on 2019-04-28 at 18:41