2008. január 3., csütörtök

Resize all controls when the parent form is resized


Problem/Question/Abstract:

How to resize all controls when the parent form is resized

Answer:

I would like to describe a technique I used in order to resize (with recursion) all the child controls of a TForm when the user resizes the form itself. I think it works relatively well, at least for my requirements. The key is the implementation of the OnResize event of the TForm. Every time the TForm is resized we chnage from an initial height PH to a final height HH and from an initial width PW to a final width WW.

At the very beginning (for example in the OnCreate event) we register the value of PW and PH:

Form1.FormCreate(Sender: TObject);
begin
  { ... }
  PW := Form1.ClientWidth;
  PH := Form1.ClientHeight;
  { ... }
end;

Then in the implementation of the OnResize event we have:

Form1.FormResize(Sender: TObject);
begin
  WW := Form1.ClientWidth; {new value of width after the resizing}
  HH := Form1.ClientHeight; {new value of height after the resizing}
  {put your cyclically resizing code here}
  {actual dimension: they will be the old dimensions in the next resizing }
  PW := WW;
  PH := HH;
end;

Now let's go deep into details with the cyclically resizing code. This is the code we place inside the implementation of the OnResize event of the TForm:

{ ... }
for I := 0 to (Form1.ControlCount - 1) do
begin
  ChangeDims(Form1.Controls[i], WW, PW, HH, PH);
end;

"ChangeDims" is the procedure which resizes (with recursion) the child controls.

procedure ChangeDims(Control: TControl; WW, PW, HH, PH: Integer);
var
  L, T, W, H: integer;
begin
  if Control is TWinControl then
    (Control as TWinControl).DisableAlign;
  try
    if Control is TWinControl then
      ScaleCtrls(Control as TWinControl, WW, PW, HH, PH);
    L := MulDiv(Control.Left, WW, PW);
    T := MulDiv(Control.Top, HH, PH);
    W := MulDiv(Control.Left + Control.Width, WW, PW) - L;
    H := MulDiv(Control.Top + Control.Height, HH, PH) - T;
    Control.SetBounds(L, T, W, H);
  finally
    if Control is TWinControl then
      (Control as TWinControl).DisableAlign;
  end;
end;

procedure ScaleCtrls(Control: TWinControl; WW, PW, HH, PH: Integer);
var
  I: Integer;
begin
  for I := 0 to Control.ControlCount - 1 do
    ChangeDims(Control.Controls[i], WW, PW, HH, PH);
end;

By making the above mentioned changes to your Form you can achieve the autoresizing of all child controls on a form. After verifing the validity of the code I encapsulated its functionality in a component. I didn't want to fill every time with extra code my form's implementation: I wanted to drop a component to my form and stop. This component assign the implementation of the OnResize event of the form after having assigned the aventually already present OnResize implementation. As a consequence the OnResize event of the Form will consist in the call to the user defined implementation (if any) followed by the above mentioned implementation which is responsible for the resizing. In this way you can assign your implementation to the OnResize event of the Form avoiding its hiding because of the presence of the cyclically resizing code.

Here is the complete code for the component:

unit Resize;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  { THackControl = class(TControl); }

  TResize = class(TComponent)
  private
    { Private declarations }
    FForm1: TForm;
    PW, PH: integer;
    FOnResizeEvent: TNotifyEvent;
    procedure ScaleCtrls(Control: TWinControl; WW, PW, HH, PH: Integer);
    procedure ChangeDims(Control: TControl; WW, PW, HH, PH: Integer);
    procedure OnResizeEvent(Sender: TObject);
    procedure Resize();
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  published
    { Published declarations }
  end;

procedure Register;

implementation

constructor TResize.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  if csDesigning in ComponentState then
    Exit;
  FForm1 := (Owner as TForm);
  PW := FForm1.ClientWidth;
  PH := FForm1.ClientHeight;
  if Assigned(FForm1.OnResize) then
  begin
    FOnResizeEvent := FForm1.OnResize;
  end;
  FForm1.OnResize := OnResizeEvent;
end;

destructor TResize.Destroy();
begin
  if not (csDesigning in ComponentState) then
    FForm1.OnResize := FOnResizeEvent;
  inherited Destroy();
end;

procedure TResize.ScaleCtrls(Control: TWinControl; WW, PW, HH, PH: Integer);
var
  I: Integer;
begin
  for I := 0 to Control.ControlCount - 1 do
    ChangeDims(Control.Controls[i], WW, PW, HH, PH);
end;

procedure TResize.ChangeDims(Control: TControl; WW, PW, HH, PH: Integer);
var
  L, T, W, H: integer;
begin
  if Control is TWinControl then
    (Control as TWinControl).DisableAlign;
  try
    if Control is TWinControl then
      ScaleCtrls(Control as TWinControl, WW, PW, HH, PH);
    L := MulDiv(Control.Left, WW, PW);
    T := MulDiv(Control.Top, HH, PH);
    W := MulDiv(Control.Left + Control.Width, WW, PW) - L;
    H := MulDiv(Control.Top + Control.Height, HH, PH) - T;
    { THackControl(Control).Font.Size := (PW div WW) * THackControl(Control).Font.Size;}
    Control.SetBounds(L, T, W, H);
  finally
    if Control is TWinControl then
      (Control as TWinControl).DisableAlign;
  end;
end;

procedure TResize.OnResizeEvent(Sender: TObject);
begin
  if Assigned(FOnResizeEvent) then
  begin
    FOnResizeEvent(Sender);
  end;
  Resize();
end;

procedure TResize.Resize();
var
  L, T, R, B, W, H, WW, HH, I: Integer;
begin
  WW := FForm1.ClientWidth;
  HH := FForm1.ClientHeight;
  for I := 0 to (FForm1.ControlCount - 1) do
  begin
    ChangeDims(FForm1.Controls[i], WW, PW, HH, PH);
  end;
  PW := WW;
  PH := HH;
end;

procedure Register;
begin
  RegisterComponents('Carlo Pasolini', [TResize]);
end;

end.

Nincsenek megjegyzések:

Megjegyzés küldése