Delphi, GExpertsComments Off on Experimental GExperts Version 1.38 2015-12-22 released
Dec232015
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:
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
Windows 8.1Comments Off on WinHlp32 for Windows 8.1
Dec202015
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:
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?
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?
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.
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}
German OnlyComments Off on [German only] Universeller Einkaufsgutschein
Dec192015
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:
Englisch:
Ich stelle diese Anleitung hiermit zur freien Verwendung als Download zur Verfügung und zwar als Open Office Text und PDF.
Delphi, GExpertsComments Off on Making a Delphi form sizable without changing BorderStyle
Dec142015
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.
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”:
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:
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.