Experimental GExperts Version 1.38 2015-12-22 released

 Delphi, GExperts  Comments Off on Experimental GExperts Version 1.38 2015-12-22 released
Dec 232015
 

Just in time for the holidays, here it is, the Christmas release of my experimental GExperts version.

You might already have read my blog posts about the work on some of the new features, but let’s start with the “boring” one: I fixed the Unicode support again, this time actually the non-Unicode support because the latest changes had broken the formatter in Delphi 6 and 7.

On the more feature-ish side there are now some more dialogues that are being made resizeable if you enabled it in the configuration on the IDE tab:

GExperts Configuration-resize-options

Also, I split the options into “Allow resize” and “Remember position”. I for one disable remembering the position because in a multi monitor set-up, it’s more of an annoyance than a feature. Getting the Options dialogue in Delphi 6 an 7 to be resizeable was a bit of a challenge.

Most of you won’t care about the other two features, but I implemented them anyway: GExperts now shows an icon on the IDE splash screen

Delphi7-SplashScreen-with-plugins

as well as an entry on the IDE about dialog.

D10-About-Dialog-with-GExperts-entry

The latter currently only for Delphi 2005 and later, but I might also think of a way to do the same for Delphi 6 and 7.

I also updated the Experimental GExperts page to reflect the latest changes. It now also includes an animated GIF demonstrating how to configure and use the Code Formatter.

 Posted by on 2015-12-23 at 12:47

WinHlp32 for Windows 8.1

 Windows 8.1  Comments Off on WinHlp32 for Windows 8.1
Dec 202015
 

Microsoft dropped support for the old WinHelp (*.hlp) file format in Windows 7 (or was it in Vista?). They provided a download that added the missing WinHlp32.exe (and probably quite a few other files) back so we could display .hlp files again. Today I had the need to do that on Windows 8.1. Unfortunately I run into several issues:

  • There are multiple downloads and it isn’t trivial to find the right one (Update for Windows 8.1 for x64-based Systems (KB917607) worked for me)
  • The download is a .msu file which doesn’t install if your windows does not have one of the supported language packs. If you get the error “The update does not apply to your system”, install the English-US language pack (I didn’t have that, I used English-GB.)
  • The installer package takes forever to prepare, then asks you whether you want to install KB917607. After a while it looks as if it is finished. I tried to open a .hlp file and it still didn’t work. Looking closer revealed yet another window open requiring me to acknowledge the license terms. So I did that and lo and behold: I could finally view .hlp files again.

Why does Microsoft make everything so complicated?

 Posted by on 2015-12-20 at 19:37

Adding an icon to the Delphi IDE splash screen

 Delphi  Comments Off on Adding an icon to the Delphi IDE splash screen
Dec 202015
 

In a previous blog post I described how to add an entry to the Delpih IDE About dialog. This time it’s about the splash screen.

Since Delphi 2005 the IDE provides the SplashScreenServices interface that is available even before the other interfaces that make up the ToolsAPI. It allows adding an icon to the splash screen quite easily:

uses
  ToolsAPI;

// [...]

procedure AddPluginToSplashScreen(_Icon: HBITMAP; const _Title: string; const _Version: string);
{$IFDEF GX_VER170_up}
// Only Delphi 2005 and up support the splash screen services
begin
  if Assigned(SplashScreenServices) then
    SplashScreenServices.AddPluginBitmap(_Title,
      _Icon, False, _Version);
end;
{$ELSE GX_VER170_up}
begin
end;

So, I could now stop writing this article if it weren’t for the case that GExperts supports Delphi 6 and 7 as well and I am stubborn enough to want that functionality for these as well. The SplashScreenServices didn’t exist back then, but it is still possible to do the same even though not as easily as the above.

It’s still no rocket science. Basically, it’s the following steps:

  • Find the splash screen form.
  • Decide where to place the icon and text.
  • Add the necessary controls.

For getting the splash screen, we use the global Screen object which is available to all IDE plugins. It’s just a matter of enumerating all existing forms for which the class provides the Forms and FormCount properties:

for i := 0 to Screen.FormCount - 1 do begin
  frm := Screen.Forms[i];
  if (frm.Name = 'SplashScreen') and frm.ClassNameIs('TForm') then begin
    // we've got it
  end;

(Of course it took me some digging to find out what the form is called and what class it is, but it isn’t rocket science either. (Why do I come back to rocket science all the time?))

Now, where do we place the additional information?

Delphi6-SplashScreen

Delphi7-SplashScreen

There is space for about 4 lines with 32×32 bitmaps and two lines of text to the right of the 6 or 7 respectively. Some experimenting gave me the pixel position: X=140, Y=150.

So, all we need to do is add three controls, one TImage and two labels, to the form, fill in the necessary properties and be done.

  imgLogo := TImage.Create(frm);
  imgLogo.Name := PluginLogoStr + IntToStr(PluginIdx);
  imgLogo.Parent := frm;
  imgLogo.AutoSize := True;
  imgLogo.Picture.Bitmap.Handle := _Icon;
  imgLogo.Left := XPOS;
  imgLogo.Top := YPOS + 32 * PluginIdx;

  lblTitle := TLabel.Create(frm);
  lblTitle.Name := 'PluginTitle' + IntToStr(PluginIdx);
  lblTitle.Parent := frm;
  lblTitle.Caption := _Title;
  lblTitle.Top := imgLogo.Top;
  lblTitle.Left := imgLogo.Left + 32 + 8;
  lblTitle.Transparent := True;
  lblTitle.Font.Color := clWhite;
  lblTitle.Font.Style := [fsbold];

  lblVersion := TLabel.Create(frm);
  lblVersion.Name := 'PluginVersion' + IntToStr(PluginIdx);
  lblVersion.Parent := frm;
  lblVersion.Caption := _Version;
  lblVersion.Top := imgLogo.Top + lblTitle.Height;
  lblVersion.Left := imgLogo.Left + 32 + 20;
  lblVersion.Transparent := True;
  lblVersion.Font.Color := clWhite;

All controls use the form as the owner, so they will be freed automatically. I chose to use unique names for the controls, it would have been easier to just set the name to empty but we’ll come to that in a second. Assigning a HBitmap to a TImage is done by assigning it to the Picture.Bitmap.Handle property. I set the font color to white because black didn’t look too nice. Also I made the title bold. Oh, and of course I made the labels transparent, so they don’t look too ugly.

Now, why use unique names for the controls? Because I was thinking about reuse. I need it for GExperts now, but why not make it usable for other plugins as well? Like my Delphi IDE explorer Expert for which there is also a Delphi 6 and 7 version now.

As said above, there is space for 4 such entries. We will assume that we never need more than that for now. 😉

By using unique control names it is easy to find out how many of the slots are already taken and add another one:

  PluginIdx := 0;
  while frm.FindComponent(PluginLogoStr + IntToStr(PluginIdx)) <> nil do
    Inc(PluginIdx);

Putting it all together gives us a reusable procedure which we can add to any plugin without even thinking about Delphi versions:

procedure AddPluginToSplashScreen(_Icon: HBITMAP; const _Title: string; const _Version: string);
{$IFDEF GX_VER170_up}
// Only Delphi 2005 and up support the splash screen services
begin
  if Assigned(SplashScreenServices) then
    SplashScreenServices.AddPluginBitmap(_Title,
      _Icon, False, _Version);
end;
{$ELSE GX_VER170_up}
const
  XPOS = 140;
  YPOS = 150;
  PluginLogoStr = 'PluginLogo';
var
  imgLogo: TImage;
  lblTitle: TLabel;
  lblVersion: TLabel;
  i: integer;
  PluginIdx: integer;
  frm: TCustomForm;
begin
  for i := 0 to Screen.FormCount - 1 do begin
    frm := Screen.Forms[i];
    if (frm.Name = 'SplashScreen') and frm.ClassNameIs('TForm') then begin
      PluginIdx := 0;
      while frm.FindComponent(PluginLogoStr + IntToStr(PluginIdx)) <> nil do
        Inc(PluginIdx);

      imgLogo := TImage.Create(frm);
      imgLogo.Name := PluginLogoStr + IntToStr(PluginIdx);
      imgLogo.Parent := frm;
      imgLogo.AutoSize := True;
      imgLogo.Picture.Bitmap.Handle := _Icon;
      imgLogo.Left := XPOS;
      imgLogo.Top := YPOS + 32 * PluginIdx;

      lblTitle := TLabel.Create(frm);
      lblTitle.Name := 'PluginTitle' + IntToStr(PluginIdx);
      lblTitle.Parent := frm;
      lblTitle.Caption := _Title;
      lblTitle.Top := imgLogo.Top;
      lblTitle.Left := imgLogo.Left + 32 + 8;
      lblTitle.Transparent := True;
      lblTitle.Font.Color := clWhite;
      lblTitle.Font.Style := [fsbold];

      lblVersion := TLabel.Create(frm);
      lblVersion.Name := 'PluginVersion' + IntToStr(PluginIdx);
      lblVersion.Parent := frm;
      lblVersion.Caption := _Version;
      lblVersion.Top := imgLogo.Top + lblTitle.Height;
      lblVersion.Left := imgLogo.Left + 32 + 20;
      lblVersion.Transparent := True;
      lblVersion.Font.Color := clWhite;
    end;
  end;
end;
{$ENDIF GX_VER170_up}

And the result looks like this:
Delphi7-SplashScreen-with-plugins

 Posted by on 2015-12-20 at 17:50

[German only] Universeller Einkaufsgutschein

 German Only  Comments Off on [German only] Universeller Einkaufsgutschein
Dec 192015
 

Mir geht es ziemlich auf den Keks, dass in letzter Zeit alles per Gutschein bezahlt werden soll bzw. immer wieder Einkaufsgutscheine als Geschenk gewĂŒnscht werden. Einkaufsgutscheine haben eigentlich nur Nachteile:

  • Ein Gutschein ist an den Aussteller gebunden und wird nur von ihm eingelöst.
  • Wenn man einen Gutschein nur teilweise einlöst, bekommt man wieder einen Gutschein, der wieder die gleichen Nachteile hat.
  • Gutscheine sind maximal 3 Jahre gĂŒltig.
  • Wenn der Aussteller pleite geht, werden Gutscheine wertlos.
  • Viele Gutscheine sind nur unzureichend gegen Betrug geschĂŒtzt.
  • Einen Gutschein muss man sich erstmal ausstellen lassen.
  • Teilweise sind Gutscheine nicht ĂŒbertragbar (obwohl das eigentlich unzulĂ€ssig ist).

Dabei ist das alles völlig unnötig, denn es gibt schon seit Jahrhunderten universelle Einkaufsgutscheine, Bargeld genannt. Sie haben keinen der o.g. Nachteile, sind also z.B. unbegrenzt gĂŒltig und können ĂŒberall eingelöst werden.

Aus diesem Grund habe ich das die folgende Anleitung zum universellen Einkaufsgutschein geschrieben. Sie bezieht sich auf den weit verbreiteten universellen Einkaufsgutschein der Vereinigten Staaten von Amerika, genannt US-Dollar.


Ihr universeller Einkaufsgutschein

Herzlichen GlĂŒckwunsch zum Erwerb des universellen Einkaufsgutscheins der Vereinigten Staaten von Amerika. Sie haben ein QualitĂ€tsprodukt mit jahrhundertelanger Geschichte erworben. Wir möchten Ihnen im Folgenden einige Hinweise zu dessen Verwendung geben.

Der beiliegende universelle Einkaufsgutschein wird in allen GeschĂ€ften in den Vereinigten Staaten von Amerika sowie weltweit vielen weiteren Annahmestellen akzeptiert. Er kann fĂŒr EinkĂ€ufe zum aufgedruckten Wert in US-Dollar verwendet werden. Restguthaben werden in weiteren universellen
Einkaufsgutscheinen oder ggf. auch WertmĂŒnzen ausgegeben, die ihrerseits wieder wie der originale universelle Einkaufsgutschein verwendet werden können. Der universelle Einkaufsgutschein ist frei auf andere Personen ĂŒbertragbar und unbegrenzt gĂŒltig. Es ist auch möglich, den universellen Einkaufsgutschein in Bargeld (z.B. Euro) umzutauschen, dabei werden ggf. WechselgebĂŒhren fĂ€llig. Der Aussteller des universellen Einkaufsgutscheins – die Vereinigten Staaten von Amerika – garantieren dessen Einlösung.

Hier noch einmal Ihre Vorteile auf einen Blick:

andere Einkaufsgutscheine universeller Einkaufsgutschein
Akteptanz Herausgeber fast ĂŒberall
GĂŒltigkeit 3 Jahre ab Ausstellung unbegrenzt
Barauszahlung in der Regel nicht möglich jederzeit möglich
Übertragung begrenzt möglich jederzeit möglich

WeiterfĂŒhrende Informationen:

Deutsch:QRCode-US$-Deutsch

Englisch:QRCode-US$-Englisch


Ich stelle diese Anleitung hiermit zur freien Verwendung als Download zur VerfĂŒgung und zwar als Open Office Text und PDF.

 Posted by on 2015-12-19 at 14:44

Making a Delphi form sizable without changing BorderStyle

 Delphi, GExperts  Comments Off on Making a Delphi form sizable without changing BorderStyle
Dec 142015
 

Don’t you just hate it when you sit in front of a 24″ monitor and have to use a form that is the size of a postage stamp?

GExperts has got a nifty feature where it makes forms of the Delphi IDE sizable that aren’t by default.

GExperts-configuration-ide-enhancements-enhance-dialogs

This works only for some forms and I wondered why it didn’t for others. So I tried to add some more (Yes, I am doing this for fun.). The primary candidates were the Search -> Find and Search -> Replace dialogs. They are sizable in Delphi >=7 but not in Delphi 6.
All I had to do was change some component properties …

procedure TManagedForm.MakeSearchFormResizable(Form: TCustomForm);
var
  frm: TForm;
  i: Integer;
  cmp: TComponent;
begin
  // This is only ever called in Delphi6 because the Search form of later
  // versions is already resizable.
  frm := TForm(Form);
  FMinHeight := -frm.Height;
  FMinWidth := frm.Width;
  frm.OnCanResize := FormCanResize;
  for i := 0 to Form.ComponentCount - 1 do begin
    cmp := Form.Components[i];
    if cmp is TPageControl then begin
      TPageControl(cmp).Anchors := [akLeft, akTop, akRight, akBottom];
    end else if cmp is TButton then begin
      TButton(cmp).Anchors := [akRight, akBottom];
    end else if cmp is TCustomComboBox then begin
      TCombobox(cmp).Anchors := [akLeft, akRight, akTop];
    end else if cmp is TGroupBox then begin
      if cmp.Name = 'GroupBox4' then begin
        // stupid developer forgot to give it a meaningful name 🙁
        TGroupBox(cmp).Anchors := [akLeft, akRight, akTop];
      end;
    end;
  end;
end;

procedure TManagedForm.MakeReplaceFormResizable(Form: TCustomForm);
var
  frm: TForm;
  i: Integer;
  cmp: TComponent;
begin
  // This is only ever called in Delphi6 because the Replace form of later
  // versions is already resizable.
  frm := TForm(Form);
  FMinHeight := -frm.Height;
  FMinWidth := frm.Width;
  frm.OnCanResize := FormCanResize;
  for i := 0 to Form.ComponentCount - 1 do begin
    cmp := Form.Components[i];
    if cmp is TPageControl then begin
      TPageControl(cmp).Anchors := [akLeft, akTop, akRight, akBottom];
    end else if cmp is TButton then begin
      TButton(cmp).Anchors := [akRight, akBottom];
    end else if cmp is TCustomComboBox then begin
      TCombobox(cmp).Anchors := [akLeft, akRight, akTop];
    end;
  end;
end;

… add an OnCanResize event handler to it that makes sure the form doesn’t get resized too small …

procedure TManagedForm.FormCanResize(Sender: TObject; var NewWidth, NewHeight: Integer;
  var Resize: Boolean);
begin
  if NewWidth < FMinWidth then
    NewWidth := FMinWidth;
  if (FMinHeight < 0) or (NewHeight < FMinHeight) then
    NewHeight := Abs(FMinHeight);
end;

… and change the border style to bsSizable.

  Form.BorderStyle := bsSizable;

At least that’s what I thought, but unfortunately this resulted in the exception “Cannot change visiblity in OnShow or OnHide”:

Cannot change visiblity in OnShow or OnHide

Looking at the VCL sources it seemed to be simply a case of removing fsShowing from the FormState, then changing the BorderStyle and afterwards adding fsShowing to FormState again. And lo and behold: It worked – kind of. I also had to recreate the form handle and assign it to my internal structures because changing the BorderStyle closes the handle.

type
  TCustomFormHack = class(TCustomForm)
  end;

// [...]
    WasShowing := (fsShowing in TCustomFormHack(Form).FFormState);
    if WasShowing then
      Exclude(TCustomFormHack(Form).FFormState, fsShowing);
// some other stuff
    if WasShowing then begin
      Form.BorderStyle := bsSizeable;
      Form.HandleNeeded;
      Handle := Form.Handle;
    end;

Thus emboldened, I went on to the more complex Tools -> Environment Options dialog. This dialog is sizable in Delphi 2005 and up, but not in Delphi 6 and 7. It was a bit more involved to make its controls move sensibly when it was resized:

procedure TManagedForm.MakePasEnvironmentDialogResizable(Form: TCustomForm);
var
  VariableOptionsFrame: TWinControl;

  function IsInVarOptionsFrame(_cmp: TComponent): boolean;
  begin
    Result := Assigned(VariableOptionsFrame) and (_cmp is TControl);
    if Result then
      Result := VariableOptionsFrame.ContainsControl(TControl(_cmp));
  end;

  procedure HandleComponent(_cmp: TComponent);
  var
    i: Integer;
  begin
    if _cmp.ClassNameIs('TVariableOptionsFrame') then begin
      if SameText(_cmp.Name, 'VariableOptionsFrame') then begin
        VariableOptionsFrame := TWinControl(_cmp);
        VariableOptionsFrame.Align := alClient;
      end;
    end else if _cmp is TPageControl then begin
      TPageControl(_cmp).Anchors := [akLeft, akTop, akRight, akBottom];
    end else if _cmp is TPanel then begin
      if SameText(_cmp.Name, 'ToolListPanel') or IsInVarOptionsFrame(_cmp) then
        TPanel(_cmp).Anchors := [akLeft, akTop, akRight, akBottom];
    end else if _cmp is TButton then begin
      TButton(_cmp).Anchors := [akRight, akBottom];
    end else if _cmp is TColorBox then begin
      if SameText(_cmp.Name, 'cbColorPicker') then
        TColorBox(_cmp).Anchors := [akLeft, akBottom];
    end else if _cmp is TCustomComboBox then begin
      if SameText(_cmp.Name, 'ecLibraryPath') or SameText(_cmp.Name, 'ecDLLOutput')
        or SameText(_cmp.Name, 'ecDCPOutput') or SameText(_cmp.Name, 'ecBrowsing') then begin
        TCombobox(_cmp).Anchors := [akLeft, akRight, akTop];
      end;
    end else if _cmp is TGroupBox then begin
      if SameText(_cmp.Name, 'GroupBox10') or SameText(_cmp.Name, 'Repository') then begin
        TGroupBox(_cmp).Anchors := [akLeft, akTop, akRight];
      end else if SameText(_cmp.Name, 'gbColors') then begin
        TGroupBox(_cmp).Anchors := [akLeft, akTop, akBottom];
      end else if IsInVarOptionsFrame(_cmp) then begin
        if SameText(_cmp.Name, 'GroupBox1') then begin
          TGroupBox(_cmp).Anchors := [akLeft, akTop, akBottom];
        end else if SameText(_cmp.Name, 'GroupBox2') then begin
          TGroupBox(_cmp).Anchors := [akLeft, akBottom];
        end;
      end;
    end else if _cmp is TListBox then begin
      if SameText(_cmp.Name, 'lbColors')  then begin
        TListBox(_cmp).Anchors := [akLeft, akTop, akBottom];
      end else if SameText(_cmp.Name, 'PageListBox') then begin
        TListBox(_cmp).Anchors := [akLeft, akTop, akBottom];
        if (TListBox(_cmp).Items.Count = 0) and (FItems.Count <> 0) then
          TListBox(_cmp).Items.Assign(FItems);
      end;
    end else if _cmp.ClassNameIs('TPropEdit') then begin
      if SameText(_cmp.Name, 'ecRepositoryDir') then begin
        TCustomEdit(_cmp).Anchors := [akLeft, akTop, akRight];
      end;
    end else if _cmp.ClassNameIs('THintListView') then begin
      if IsInVarOptionsFrame(_cmp) then
        TWinControl(_cmp).Anchors := [akLeft, akTop, akRight];
    end;
    for i := 0 to _cmp.ComponentCount - 1 do begin
      HandleComponent(_cmp.Components[i]);
    end;
  end;

var
  frm: TForm;
begin
  // This is called in Delphi6 and Delphi 7 because the Environment form of later
  // versions is already resizable.
  frm := TForm(Form);
  FMinHeight := frm.Height;
  FMinWidth := frm.Width;
  frm.OnCanResize := FormCanResize;
  HandleComponent(frm);
end;

It too seemed to work nicely, until I came across this oddity:

Environment-Options-Palette

The Pages list, that normally contains all tabs of the component palette, was empty. It turned out that this was because the form’s handle was closed and recreated (This is a known VCL problem which still exists in some later Delphi versions. I have no idea if it has been fixed by now.). Trying to store the list in a StringList and assign it later did not solve the problem because the ListBox.Items.Objects[] contained pointers to a rather complex data structure that were also lost.

I was out of ideas and turned to the trusty StackOverflow community (of course I tried to Google this question first, but nothing turned up.) who gave me this answer which I turned into this method:

procedure TManagedForm.ForceVisibleToBeSizable(WndHandle: HWND);
begin
  // this is taken from http://stackoverflow.com/a/34255563/49925
  SetWindowLong(WndHandle, GWL_STYLE,
    GetWindowLong(WndHandle, GWL_STYLE) and not WS_POPUP or WS_THICKFRAME);
  SetWindowLong(WndHandle, GWL_EXSTYLE,
    GetWindowLong(WndHandle, GWL_EXSTYLE) and not WS_EX_DLGMODALFRAME);
  InsertMenu(GetSystemMenu(WndHandle, False), 1, MF_STRING or MF_BYPOSITION, SC_SIZE, 'Size');
  SetWindowPos(WndHandle, 0, 0, 0, 0, 0,
    SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_FRAMECHANGED);
  DrawMenuBar(WndHandle);
end;

So, now all is well. The Environment Options dialog is sizable.

Sizable-Environment Options

 Posted by on 2015-12-14 at 23:20