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.