Difference between revisions of "Remember form position and size"

From Free Pascal wiki
(Created page with "=Simple example= A basic approach to keep form position and size between application restart is to store these state values to some persistent storage like XML file, INI file...")
 
Line 1: Line 1:
=Simple example=
+
==Simple example==
  
 
A basic approach to keep form position and size between application restart is to store these state values to some persistent storage like XML file, INI file or Windows registry.
 
A basic approach to keep form position and size between application restart is to store these state values to some persistent storage like XML file, INI file or Windows registry.

Revision as of 14:00, 18 April 2015

Simple example

A basic approach to keep form position and size between application restart is to store these state values to some persistent storage like XML file, INI file or Windows registry. You need at least store TForm properties Left, Top, Width and Height. But if form is maximized state you need also store values of TForm RestoredLeft, RestoredTop, RestoredWidth and RestoredHeight so form will be correctly sized if user click on maximize botton again to switch back to normal state.

For simplification TXMLConfig component is used as persistent storage. Place this component on your form and set its filename property to Config.xml.

procedure TForm1.StoreFormState;
begin
  with XMLConfig1 do begin
    SetValue('NormalLeft', Left);
    SetValue('NormalTop', Top);
    SetValue('NormalWidth', Width);
    SetValue('NormalHeight', Height);

    SetValue('RestoredLeft', RestoredLeft);
    SetValue('RestoredTop', RestoredTop);
    SetValue('RestoredWidth', RestoredWidth);
    SetValue('RestoredHeight', RestoredHeight);

    SetValue('WindowState', Integer(WindowState));
  end;
end;

Restoration of stored form properties is a little bit complicated as properties have to be set in right order to prevent form resize animation or values to be overwritten by internal form mechanism. There is a rule that if normal and restored bounds are equal then form was in normal state. If normal and restored bounds are different then form is probably in maximized state and restored bounds are set to original position and size before maximization. As we have stored WindowState then we can use it to decide if form is in maximized state. To restore maximized state we have to first set form to normal state then set TForm.BoundsRect and after that set wsMaximized state. This is because Restored properties are read only and we can set them directly.

procedure TForm1.RestoreFormState;
var
  LastWindowState: TWindowState;
begin
  with XMLConfig1 do begin
    LastWindowState := TWindowState(GetValue('WindowState', Integer(WindowState)));

    if LastWindowState = wsMaximized then begin
      WindowState := wsNormal;
      BoundsRect := Bounds(
        GetValue('RestoredLeft', RestoredLeft),
        GetValue('RestoredTop', RestoredTop),
        GetValue('RestoredWidth', RestoredWidth),
        GetValue('RestoredHeight', RestoredHeight));
      WindowState := wsMaximized;
    end else begin
      WindowState := wsNormal;
      BoundsRect := Bounds(
        GetValue('NormalLeft', Left),
        GetValue('NormalTop', Top),
        GetValue('NormalWidth', Width),
        GetValue('NormalHeight', Height));
    end;
  end;
end;

Functions should be executed from OnShow and OnClose handlers.

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  StoreFormState;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  RestoreFormState;
end;


Support for more forms

To support storing state for multiple forms we need to extend our functions to support Form parameter and allow store their values to different XMLConfig paths.

procedure TForm1.StoreFormState(Form: TForm);
begin
  with XMLConfig1 do begin
    SetValue('Forms/' + Form.Name + '/NormalLeft', Form.Left);
    SetValue('Forms/' + Form.Name + '/NormalTop', Form.Top);
    SetValue('Forms/' + Form.Name + '/NormalWidth', Form.Width);
    SetValue('Forms/' + Form.Name + '/NormalHeight', Form.Height);

    SetValue('Forms/' + Form.Name + '/RestoredLeft', Form.RestoredLeft);
    SetValue('Forms/' + Form.Name + '/RestoredTop', Form.RestoredTop);
    SetValue('Forms/' + Form.Name + '/RestoredWidth', Form.RestoredWidth);
    SetValue('Forms/' + Form.Name + '/RestoredHeight', Form.RestoredHeight);

    SetValue('Forms/' + Form.Name + '/WindowState', Integer(Form.WindowState));
  end;
end;

procedure TForm1.RestoreFormState(Form: TForm);
var
  LastWindowState: TWindowState;
begin
  with XMLConfig1 do begin
    LastWindowState := TWindowState(GetValue('Forms/' + Form.Name + '/WindowState', Integer(Form.WindowState)));

    if LastWindowState = wsMaximized then begin
      Form.WindowState := wsNormal;
      Form.BoundsRect := Bounds(
        GetValue('Forms/' + Form.Name + '/RestoredLeft', Form.RestoredLeft),
        GetValue('Forms/' + Form.Name + '/RestoredTop', Form.RestoredTop),
        GetValue('Forms/' + Form.Name + '/RestoredWidth', Form.RestoredWidth),
        GetValue('Forms/' + Form.Name + '/RestoredHeight', Form.RestoredHeight));
      Form.WindowState := wsMaximized;
    end else begin
      Form.WindowState := wsNormal;
      Form.BoundsRect := Bounds(
        GetValue('Forms/' + Form.Name + '/NormalLeft', Form.Left),
        GetValue('Forms/' + Form.Name + '/NormalTop', Form.Top),
        GetValue('Forms/' + Form.Name + '/NormalWidth', Form.Width),
        GetValue('Forms/' + Form.Name + '/NormalHeight', Form.Height));
    end;
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  StoreFormState(Self);
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  RestoreFormState(Self);
end;

Minimized and Fullscreen states

TForm.WindowState have two additional states wsMinimized and wsFullscreen. These special states need special care if they should be supported.

Ensure that restored form is visible

As size of screen can change in time we need to check restored form position and size to ensure that form is visible to user and so your can manipulate with them.

function TForm1.CheckFormIsEntireVisible(Rect: TRect): TRect;
var
  Width: Integer;
  Height: Integer;
begin
  Result := Rect;
  Width := Rect.Right - Rect.Left;
  Height := Rect.Bottom - Rect.Top;
  if Result.Left < (Screen.DesktopLeft) then begin
    Result.Left := Screen.DesktopLeft;
    Result.Right := Screen.DesktopLeft + Width;
  end;
  if Result.Right > (Screen.DesktopLeft + Screen.DesktopWidth) then begin
    Result.Left := Screen.DesktopLeft + Screen.DesktopWidth - Width;
    Result.Right := Screen.DesktopLeft + Screen.DesktopWidth;
  end;
  if Result.Top < Screen.DesktopTop then begin
    Result.Top := Screen.DesktopTop;
    Result.Bottom := Screen.DesktopTop + Height;
  end;
  if Result.Bottom > (Screen.DesktopTop + Screen.DesktopHeight) then begin
    Result.Top := Screen.DesktopTop + Screen.DesktopHeight - Height;
    Result.Bottom := Screen.DesktopTop + Screen.DesktopHeight;
  end;
end;

If requirement for entire form surface to be visible is too strong then we could check that at least part of form is visible. Here Part is size in pixels which should be at least visible for both horizontal and vertical position and size.

function TForm1.CheckFormIsPartialVisible(Rect: TRect; Part: Integer): TRect;
var
  Width: Integer;
  Height: Integer;
begin
  Result := Rect;
  Width := Rect.Right - Rect.Left;
  Height := Rect.Bottom - Rect.Top;
  if Result.Right < (Screen.DesktopLeft + Part) then begin
    Result.Left := Screen.DesktopLeft + Part - Width;
    Result.Right := Screen.DesktopLeft + Part;
  end;
  if Result.Left > (Screen.DesktopLeft + Screen.DesktopWidth - Part) then begin
    Result.Left := Screen.DesktopLeft + Screen.DesktopWidth - Part;
    Result.Right := Screen.DesktopLeft + Screen.DesktopWidth - Part + Width;
  end;
  if Result.Bottom < (Screen.DesktopTop + Part) then begin
    Result.Top := Screen.DesktopTop + Part - Height;
    Result.Bottom := Screen.DesktopTop + Part;
  end;
  if Result.Top > (Screen.DesktopTop + Screen.DesktopHeight - Part) then begin
    Result.Top := Screen.DesktopTop + Screen.DesktopHeight - Part;
    Result.Bottom := Screen.DesktopTop + Screen.DesktopHeight - Part + Height;
  end;
end;

See also