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.



