Preserving the form state

By: python python

Abstract: Here's how to use the VCL streaming system to preserve form state between invocations. By G. Goossen.

The VCL streaming system is responsible for loading the form state from the form resource, in which form state is the form's published properties and the state of the child components. Except for this, most programs don't use the VCL streaming system. This is understandable; it is a very complex and poorly documented system.

To stimulate making use of the streaming system this article will give you an example of a possible use for it. The example consists of a class that will preserve the form state so that when a form is loaded it will restore itself to the same appearance it had when it was last closed. The main advantage of using the VCL streaming system for this is that it supports so much (at least all the components on the component palette). This means that you can make almost every component adjustable without having to worry about how to save and load it.

The code

Preserving the state will be done by the following class:

  TPreserveStateForm = class(TForm)
  public
    constructor Create(AOwner: TComponent); override;
    procedure BeforeDestruction; override;
  end;

If you derive a form from this class instead of from TForm the form will automatically preserve its state. This is done by saving the form state to the file "<Application path><ClassName>.fs" when the form is destroyed and then loading the state from the file when the form is next created. Saving and loading are performed in the overridden Create and BeforeDestruction methods.

Saving the form is done using TStream method WriteComponent, which completely saves the form state. The best place to do this is in the BeforeDestruction method so nothing of the form will be destroyed.

procedure TPreserveStateForm.BeforeDestruction;
begin
  inherited;
  with TFileStream.Create(ExtractFilePath(Application.ExeName) + ClassName +
    '.fs', fmCreate) do
  try
    WriteComponent(Self);
  finally
    Free;
  end;
end;

Loading the form is done using WriteComponent's counterpart, ReadComponent. First we check that the form-state file exists. If it does not we just call the inherited constructor, which will load the form state from the form resource. If the file does exist, we initialize the form without loading the form state of the resource by calling CreateNew. We need to bypass the inherited constructor, because the streaming system requires that the form isn't modified when the form state is loaded. After the form is initialized, we load the form state from the file.

constructor TPreserveStateForm.Create(AOwner: TComponent);
begin
  if FileExists(ExtractFilePath(Application.ExeName) + ClassName + '.fs') then
  begin
    CreateNew(AOwner, 0);
    with TFileStream.Create(ExtractFilePath(Application.ExeName) + ClassName +
      '.fs', fmOpenRead or fmShareDenyWrite) do
    try
      ReadComponent(Self);
    finally
      Free;
    end;
  end
  else
    inherited Create(AOwner);
end;

That is all the code we need. However there are a few notes about using this class:

  • Although newly declared published properties in the form don't appear in Object Inspector and aren't saved by the Delphi editor, they are used in the ReadComponent and WriteComponent methods. You can make use of this fact to save additional data with the form state in the form of published properties.
  • Some properties might be modified in a way you don't want them to be saved. For example if you have a text editor, the text in it will be saved. The best way to handle this is to reset these properties in the OnDestroy event. To ensure the OnDestroy event is called before the state is saved, OldCreateOrder must be False.
  • The fsCreating flag is not included in the FormState property as it is normally when the form state is loaded from the resource. The only place in the VCL that makes use of this flag is with the TForm.Visible property which will when set to True in the state form show immediately instead of waiting until the complete form is loaded. To avoid this you might set the Visible property to False before saving the form. However, there may be some third-party components that require this flag.
  • You can use this technique for inherited forms, but the complete form state is saved and loaded. Therefore you can't use inheritance to reduce the size of the form state file.
  • If you have components that don't have associated published fields the classes of these components need to be registered using RegisterClasses.
  • Of course you should not use this class if you create multiple instances of the form.

Other uses for the streaming system

In principle you can also use the ReadComponent and WriteComponent with frames (and data modules), but unfortunately there isn't a CreateNew method in TFrame, so you can not bypass loading the frame state from the resource. I have not been able to find an acceptable way around this.

Another possible use for the streaming system is to let the user select a form state like a skin. Again we have to deal with the requirement of the streaming system that the form isn't modified. Therefore, if you want to load a new form state after startup the old form has to be destroyed and created again.

The file accompanying this article contains a unit with the described class and a demo program.
CodeCentral entry 15210

If you have any comments on this article, you can reach me at python@softhome.net.


Server Response from: SC2