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

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 referecne 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. Ca 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” 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 requesets 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

Getting the Windows version revisited

 Delphi  Comments Off on Getting the Windows version revisited
Apr 222019
 

In my last blog post Getting the Windows version, I claimed that there is no way to get the actual version number of Windows 10 without reading the version information of the Kernel32.dll and interpreting it 1.

Since then I have been told that there is actually a Registry key that contains the Windows version:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

It has several entries that can be used to get the same version info that the Winver tool displays to the user. On Windows 10 that’s:

  • ProductName
  • ReleaseId
  • CurrentBuildNumber
  • UBR

On my test installation they get me
“Windows 10 Pro” Version “1809” (OS Build “17762”.”437″)

(The quotes denote values read from the registry.)

By changing the ReleaseId value I verified that Winver (at least the on my Windows 10 installation) also reads these entries.

Some of these entries are available for older versions of Windows too, while some other entries apparently are only available in older versions. On the installations I have available for tests, it looks like this:

Entry Windows 10 Windows 8.1 Windows 7
ProductName x x x
ReleaseId x
CurrentBuildNumber x x x
UBR x x
CSDVersion x

There are some other interesting entries there, but I won’t go into that any further.

So I wrote another function that reads these entries and creates a version string from it. It returns the following results:

Windows Version Output
7 Windows 7 Professional (OS Build 7601.24411) Service Pack 1
8.1 Windows 8.1 Pro (OS Build 9600.19327)
10 Windows 10 Pro Version 1809 (OS Build 17763.437)

There is one catch though: Windows 64 bit versions by default return different view of the registry to 32 bit programs than they do to 64 bit programs. In order for a 32 bit program to read the “real” registry, it must open the registry specifying the KEY_WOW64_64KEY flag in addition to the desired access.

So here is the code:

function TryGetWindowsVersionFromRegistry(out _Values: TWinCurrentRec): Boolean;
var
  Reg: TRegistry;
begin
  // We need to read the real registry, not the 32 bit view, because some of the entries
  // don't exist there.
  Reg := TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);
  try
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    Result := Reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows NT\CurrentVersion');
    if not Result then
      Exit; //==>

    _Values.BuildLab := TRegistry_ReadString(Reg, 'BuildLab');
    _Values.BuildLabEx := TRegistry_ReadString(Reg, 'BuildLabEx');
    _Values.CSDBuildNumber := TRegistry_ReadString(Reg, 'CSDBuildNumber');
    _Values.CSDVersion := TRegistry_ReadString(Reg, 'CSDVersion');
    _Values.CurrentBuildNumber := TRegistry_ReadString(Reg, 'CurrentBuildNumber');
    _Values.CurrentVersion := TRegistry_ReadString(Reg, 'CurrentVersion');
    _Values.EditionId := TRegistry_ReadString(Reg, 'EditionId');
    _Values.ProductName := TRegistry_ReadString(Reg, 'ProductName');
    _Values.ReleaseId := TRegistry_ReadString(Reg, 'ReleaseId');
    _Values.UBR := TRegistry_ReadInteger(Reg, 'UBR');
  finally
    FreeAndNil(Reg);
  end;
end;

function GetWindowsVersionStringFromRegistry: string;
var
  Values: TWinCurrentRec;
begin
  if not TryGetWindowsVersionFromRegistry(Values) then begin
    Result := _('Unknown Windows 10 version (registry key does not exist)');
    Exit; //==>
  end;

  if Values.ProductName = '' then begin
    Result := _('Unknown Windows version (ProductName entry cannot be read)');
    Exit; //==>
  end;
  Result := Values.ProductName;
  if Values.ReleaseId <> '' then
    Result := Result + ' Version ' + Values.ReleaseId;
  if Values.CurrentBuildNumber <> '' then begin
    Result := Result + ' (OS Build ' + Values.CurrentBuildNumber;
    if Values.UBR <> 0 then
      Result := Result + '.' + IntToStr(Values.UBR);
    Result := Result + ')';
  end;
  if Values.CSDVersion <> '' then
    Result := Result + ' ' + Values.CSDVersion;
end;

That code works even with Delphi 6, but you will have to declare the constant

KEY_WOW64_64KEY        = $0100;

there since it did not exist when Delphi 6 was released. It does exist in Delphi 2007. I haven’t tried other versions.

These functions are again also available in the unit u_dzOsUtils of my dzlib library.

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

—–

1 Actually I did not want to claim that there is no way but that I could not find one.

 Posted by on 2019-04-22 at 15:17

Getting the Windows version

 Delphi  Comments Off on Getting the Windows version
Apr 212019
 

When Microsoft introduced Windows 10 they said it would be the last version of Windows ever. They lied of course, because since then we have had multiple versions of an operating system called “Windows 10”.

Even worse, starting with Windows 8 they made their WinAPI function GetVersionEx lie about the Windows version, if the program did not say it knows about the current version. Which means every program written with older versions of Delphi will report Windows 6.2 (which was the internal version number of Windows 8). Only Windows 8 – and later Windows 10 – compatible Delphi compilers added a manifest to the executables to tell these Windows versions they know about it.

Since many Delphi developers still use ancient versions (there are always a few GExperts downloads for Delphi 6 when I publish an update, and quite a few for Delphi 2007), this is an issue.

I still want the Bug Report Wizard to report the correct Windows version, and not just “Windows 10” but “Windows 10 version 1809” as the WinVer tool says. So I did some digging and found that apparently there are two methods for getting the true version number:

  1. Call the RtlGetVersion API function, which is meant to be called by drivers only and also only returns version 10.0 for Windows 10 so it isn’t of much use to me (but at least it doesn’t lie).
  2. Get the product version from the version information of one of the main Windows DLLs, e.g. kernel32.dll, and use the revision number to determine the Windows 10 version number from it.

Since I could not find any ready made code for this, I rolled my own (maybe I didn’t look hard enough).

And being a generous person I am sharing it with you, including all the bugs I probably have introduced.

It’s in the u_dzOsUtils unit of my dzlib library.

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

EDIT: See also my follow up post Getting the Windows version revisited.

 Posted by on 2019-04-21 at 20:29

A simple way to create an URL label in Delphi

 Delphi  Comments Off on A simple way to create an URL label in Delphi
Apr 192019
 

An URL label is a TLabel on a form which displays some blue, underlined text which, when clicked with the mouse, opens a web page.

This kind of labels is nothing new in Delphi, I remember using them even back in the 1990s, but they always were a pain in the lower back to create:

  • Write the URL into the caption.
  • Set the font color to blue
  • Set the font to underlined
  • Set the cursor to crHandPoint
  • Write an OnClick event handler that opens the desired URL

Lots of boring and repetitive work and on top of that a Windows API call which I kept forgetting.
This led me to simply not use them.

Alternatively there were specialized components which you could use, so you simply dropped a TUrlLabel control on a form, filled in the caption and be done. There were quite a lot of these, and every time I wanted to use them I stopped, thought about it and didn’t. Install a package into the IDE and add another 3rd party dependency to the program just to have such a simple component? Nah!

Today, I came across these labels again on the GExperts About- and Feedback forms. I had to change some of them, and being a lazy basterd™, I decided to make this process easier once and for all. And not by adding a package / unit with a custom component but writing an overloaded pair of helper procedures which I call once and which does everything for me:

procedure TLabel_MakeUrlLabel(_lbl: TLabel); overload;
procedure TLabel_MakeUrlLabel(_lbl: TLabel; const _URL: string; _SetCaption: Boolean = False); overload;

They are used by simply calling them in the form’s constructor or in FormCreate passing a TLabel as parameter:

constructor TMyForm.Create(Owner: TComponent);
begin
  inherited;

  // if the caption already contains the URL to open:
  TLabel_MakeUrlLabel(l_SomeUrlLabel);

  // if it contains some descriptive text instead:
  TLabel_MakeUrlLabel(l_AnotherUrlLabel, 'https://blog.dummzeuch.de');

  // or if the caption should just be set in this code:
  TLabel_MakeUrlLabel(l_YetAnotherUrlLabel, 'https://blog.dummzeuch.de', True);
end;

Everything is taken care of, no changes to the label’s properties and no OnClick event handler necessary.

Here is how it is implemented:

type
  TUrlLabelHandler = class(TComponent)
  private
    FLbl: TLabel;
    FUrl: string;
    procedure HandleOnClick(_Sender: TObject);
  public
    constructor Create(_lbl: TLabel; const _URL: string); reintroduce;
  end;

constructor TUrlLabelHandler.Create(_lbl: TLabel; const _URL: string);
begin
  inherited Create(_lbl);
  FLbl := _lbl;
  FUrl := _URL;
  FLbl.OnClick := HandleOnClick;
  FLbl.Font.Style := FLbl.Font.Style + [fsUnderline];
  FLbl.Font.Color := clBlue;
  FLbl.Cursor := crHandPoint;
  if (FLbl.hint = '') and (Menus.StripHotkey(FLbl.Caption) <> FUrl) then begin
    FLbl.hint := FUrl;
    FLbl.ShowHint := True;
  end;
end;

procedure TUrlLabelHandler.HandleOnClick(_Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open', PChar(FUrl), nil, nil, SW_SHOWNORMAL);
end;

procedure TLabel_MakeUrlLabel(_lbl: TLabel);
begin
  TLabel_MakeUrlLabel(_lbl, Menus.StripHotkey(_lbl.Caption), False);
end;

procedure TLabel_MakeUrlLabel(_lbl: TLabel; const _URL: string; _SetCaption: Boolean = False);
begin
  if _SetCaption then
    _lbl.Caption := _URL;
  TUrlLabelHandler.Create(_lbl, _URL);
end;

Not rocket science either. All it does is setting some properties and then it uses a simple trick to attach an OnClick event handler to the label: It creates a specialized TComponent descendant which sets the label as its owner and assigns one of its methods as the OnClick event handler. Since the component is owned by the label, it will take care of freeing it, so no further action required and no memory leak possible. The OnClick event then executes the WinApi call I mentioned above and which I kept forgetting:

  ShellExecute(Application.Handle, 'open', PChar(FUrl), nil, nil, SW_SHOWNORMAL);

I like neat tricks like those: Call and forget.

For your (or actually my own) convenience I have added it to the unit u_dzVclUtils of my dzlib. Feel free to use it.

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

 Posted by on 2019-04-19 at 19:35

Image Processing Magic

 Delphi  Comments Off on Image Processing Magic
Apr 162019
 

It’s a bit frightening what image processing “magic” can do to a picture:

Original:

Processed:

And that’s not even advanced “magic” but some rather simple code written in Delphi (most of which I can’t publish here unfortunately).

What it does is:

  • Smooth brigthness by applying a brigthness map.
  • Adjust for lens edge blur by selective sharpening.
  • Improve contrast by histogram stretching.

Of course since this is a gray scale picture, the processing is much simpler than it would be for colour pictures.

 Posted by on 2019-04-16 at 11:57

Extend the event OnClick of all MenuItems in the screen to execute another block of code

 Delphi, GExperts  Comments Off on Extend the event OnClick of all MenuItems in the screen to execute another block of code
Apr 032019
 

Shameless self promotion:

On StackOverflow somebody asked the question in the title and I answered it. And as you might have guessed, I am mighty proud of having had that idea.

It might be interesting to know that this is how GExperts fixes some of the IDE bugs and enhances some forms: It creates a TComponent descendant which hooks some events of the form to implement the fixes or enhancements. And since it is added to the form, it automatically gests freed in the form’s destructor.

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

 Posted by on 2019-04-03 at 12:18

Things that every desktop program should do

 Delphi  Comments Off on Things that every desktop program should do
Apr 032019
 

There are a few things that every single one of our internal programs should do:

  • Every dialog should be sizeable if it makes any sense at all. Delphi makes that easy with the Align and Anchor properties. Ask yourself the following questions. If you answer any of them with Yes, you should make the dialog sizeable:
    • Is there a list with an unkown number of entries?
    • Is there a text entry field which can contain very long text (e.g. a file name)?
  • If a dialog is sizeable, set a minimum window size that allows all controls to be visible in a reasonable size.
  • If a dialog is sizeable, you should test it after every change. It is embarrassing if one button does not move the way it should.
  • If a dialog is sizeable, it should store its position and size and load it when it is being opened again. But keep in mind, that the screen sizes and arrangements can vary from one computer to another and can even change on the same computer because somebody rearranged the physical positions of his monitors or got a new one.
    • Do not store the position and size centrally for all users. If you do that, some of them will hate you. I prefer HKCU in the registry for that.
    • Always make sure that the dialog is fully visible on a monitor. A dialog that opens outside the visible area is an embarrasment and also a support nightmare.
  • Make it easy for the user to find version information. I prefer having it right in the main window’s title. e.g. “My Super Application [1.0.1.532 2019-04-03]”. The date here comes from the project version which our build scripts automatically fill with the build date.

There are a few functions in the unit u_dzVclUtils of my dzlib that make this a lot easier. They are usually called in the form’s constructor:

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

 Posted by on 2019-04-03 at 11:55

Changes between GExperts 1.3.12 and 1.3.13

 Delphi, GExperts  Comments Off on Changes between GExperts 1.3.12 and 1.3.13
Mar 312019
 

I had to look them up myself, so it took a while but here they are, the changes, bugfixes and improvements between the GExperts 1.3.12 (released 2018-12-22) and 1.3.13 (released yesterday, 2019-03-30):

  • Bugfix (#105): Set Tab Order expert no longer worked with Delphi 6, 7 and 2005 (Remember what I wrote about testing these versions?)
  • Improvements to the Uses Clause Manager:
    • New configuration setting “GExperts caching directory”, used to store the cached identifiers per unit
    • Additional configuration options for the expert: Caching can now be disabled and the cache can be cleared. (Bug report #104)
    • It is now possible to use the project’s map file rather than the dpr for getting a list of used units. This includes the VCL/RTL and possibly any 3rd party units and makes the Project tab much more useful.
    • Bugfix (#110): Entries in the VCL/RTL list were clipped / overlapped.
  • Bugfix (#109): When loading a form’s position only, Width and Height no longer change every time.
  • Added additional Delphi 10.2 and 10.3 warnings to the Insert Warn Directive editor expert
  • Bugfix: The number of entries for the Favourite Files expert in the registry doubled every time the list was saved.
  • Improvements to the Grep expert:
    • Hint about separating multiple directories with semicolon.
    • It can now use the project’s map file rather than the dpr for getting the list of used units. This includes all units compiled in the project not just those explicitly added to it. There is a configuration option for enabling that.
    • Ensure that the RTL and all existing subsystem Paths (VLC, FMX, CLX) are in the drop down menu.
    • Searching DFM files can now handle strings split into multiple lines and containing special characters stored as #<number>. (Note: This is not quite finished. The display in the Results window is broken.) (Bug report #112)
  • Custom beep as a WAV file for the Proof Reader expert (Suggested by Philip Rayment, Patch by Achim Kalwa, Bug report #111).
  • Bugfix (#113): Added another check for duplicate GExperts DLLs being loaded into the same IDE to prevent error message “PrivateGXMenuActionManager is not nil upon creation”
  • Project Dependency expert:
    • Dramatically improved performance for the indirect dependencies tab. The strings and string lists are no longer stored in and read from the UI. Also string lists are now sorted so look ups are much faster.
    • Units in the project (root node of the tree view are now also listed)
    • New configuration option to search for units in the library path and also in the browsing path.
  • Bugfix (#114): Disabled editor experts still blocked the associated keyboard shortcut and could be called with this shortcut.
  • Bugfix (#117): Editor experts could be executed even if not the code editor but the form editor was active.

As you can see above, I am looking into bugs reported on Sourceforge and also feature suggestions posted there. That is my preferred way of users to report bugs and request features because it is easy for me to manage them in one place. The other channels (email via the GExperts “Send a Bug Report / Suggestion” button and the GExperts subforum in the international Delphi Praxis forum) work too, but they are much less convenient for me and some reports / requests may get lost. Also, please remember that I am working on this project in my spare time, so any additional work, e.g. creating bug reports on Sourceforge from emails I receive, takes time in which I am not working on the actual software. It’s also boring work which might discourage me from doing it and if it is getting out of hand might even make me stop working on GExperts at all.

Let me close this post by thanking all those people who have worked on GExperts before me and on whose work I have been building over the years (and whose work I have also been using as a GExperts user for more than two decades as a professional software developer). And also those who continue to contribute their work and some money to this project.

Discussion about this post in the international Delphi Praxis forum.

 Posted by on 2019-03-31 at 13:56