Mawg
Mawg

Reputation: 40205

How to save and restore a form?

So, I have a form with a few dozen controls and someone would like to save and later restore their contents and settings - which radio button was selected, what was the Position of that up/down, etc.

I would also like to store any entries added to a list box at run time.

What's the simplest way to do it? DfmToString and reverse? Write/read a .INI? Something else?

Upvotes: 18

Views: 15122

Answers (3)

IceCold
IceCold

Reputation: 21231

In my Delphi Light Saber library you will find the ccINIFile.pas which does exactly that: save the GUI state to disk simply by calling the SaveForm (Form: TForm) procedure. To restore the GUI when the application starts again, call LoadForm (Form: TForm). It really works. No other fuss. No strings attached.

Usage

  • Call SaveForm(MyForm) in TMySettingsForm.OnDestroy
  • Call LoadForm(MyForm) after the creation of TMySettingsForm

[UPDATE] After a 2025 update to my library, you don't have to call SaveForm/LoadForm anymore. The video shows how the library does that automatically for you. But if you want, you can still call SaveForm/LoadForm of your manually created forms.

Other competencies

Storing individual controls You can also save individual controls. Example: store the status of a checkbox to INI file with: TCubicIniFileVcl.Write(MyCheckBox). The user doesn’t have to provide a default value. If value does not exist in INI file, the value from GUI (MyCheckBox.Checked) will be used.

Saving the form position/size The SaveForm can take an extra boolean parameter (default False). If True, it will only save the position of the form (Left/Top, no Width/Height/WndState) and not the status of the controls.

Completeness Most common VCL controls are supported. Support for more controls can be easily added with just an if/then. Check IsSupported() to see a list of supported controls.

Important SaveFrom will fail to save the controls of a form if they have been re-parented (moved to another form). But no exception will rise. The new parent form will also fail to save them. To fix this, we need to move the controls back to their original parent (form) BEFORE we call SaveFrom!

Notices

  • LoadForm is called after the components on the form are created but BEFORE their CreateWnd is called!
  • This technique should only be used on small projects. For real projects what you should save to disk (maybe to a binary file) is the internal state of the object that is holding the data. When you need to restore that status of the GUI you should read it from the object itself.

UNICODE
INI file does not support Unicode chars. Unicode chars are replaced with ‘?’. Sorry. This is not a limitation in my code but in Delphi’s RTL.

There is also a video: https://www.youtube.com/watch?v=a9nZiDXYmIo


@Mawg Many people feels that way. JVCL is a intimidating install - big, a little more complex than "Next, Next, Finish", and a whole lot of components... I believe that actually JVCL have more code than the Delphi's RTL+VCL - I have that impression, some one can confirm? – Fabricio Araujo

Fabricio is right! JVC can be intimidating. My library is truly light-weight compared to JVCL!

Upvotes: 3

vcldeveloper
vcldeveloper

Reputation: 7489

PRUZ's solution is a ready made solution; JVCL is open-source, and using JvFormStorage is simple. But you can also use Delphi's own streaming mechanism without using any third-party components. Here is an example:

procedure SaveComponentToFile(Component: TComponent; const FileName: TFileName);
var
  FileStream : TFileStream;
  MemStream : TMemoryStream;
begin
  MemStream := nil;

  if not Assigned(Component) then
    raise Exception.Create('Component is not assigned');

  FileStream := TFileStream.Create(FileName,fmCreate);
  try
    MemStream := TMemoryStream.Create;
    MemStream.WriteComponent(Component);
    MemStream.Position := 0;
    ObjectBinaryToText(MemStream, FileStream);
  finally
    MemStream.Free;
    FileStream.Free;
  end;
end;

SaveComponentToFile takes a component instance, plus a file name, and streams the component into the file, in a human-readable text.

To load the component from file, you can use a code like this:

procedure LoadComponentFromFile(Component: TComponent; const FileName: TFileName);
var
  FileStream : TFileStream;
  MemStream : TMemoryStream;
  i: Integer;
begin
  MemStream := nil;

  if not Assigned(Component) then
    raise Exception.Create('Component is not assigned');

  if FileExists(FileName) then
  begin
    FileStream := TFileStream.Create(FileName,fmOpenRead);
    try
      for i := Component.ComponentCount - 1 downto 0 do
      begin
        if Component.Components[i] is TControl then
          TControl(Component.Components[i]).Parent := nil;
        Component.Components[i].Free;
      end;

      MemStream := TMemoryStream.Create;
      ObjectTextToBinary(FileStream, MemStream);
      MemStream.Position := 0;
      MemStream.ReadComponent(Component);
      Application.InsertComponent(Component);
    finally
      MemStream.Free;
      FileStream.Free;
    end;
  end;
end;

LoadComponentFromFile takes a component instance, and a file name, then loads file content into the component instance. To avoid naming conflict, we are free all existing owned components of the instance, before loading file data into it.

Now you can use the above code for saving a form into a file:

  SaveComponentToFile(FSecondForm,ExtractFilePath(Application.ExeName)+ 'formdata.txt');

FSecondForm is a form instance, and it will be saved into "formdata.txt" file inside the same folder as the EXE file.

And to load FSecondForm from "formdata.txt" file, we write this:

  if not Assigned(FSecondForm) then
    FSecondForm := TfrmSecond.Create(Application);
  LoadComponentFromFile(FSecondForm,ExtractFilePath(Application.ExeName)+ 'formdata.txt');
  FSecondForm.Show;

LoadComponentFromFile needs the instance to be created first, so we check if FSecondForm is assigned, if not, we create an instance of it (it is an instance of TfrmSecond class), and then load file data into it. And eventually, we show the loaded form.

Upvotes: 28

avra
avra

Reputation: 3730

It is pretty easy to read/write component or object properties, or forms position in INI file or registry. Everything you need exist in help. You just need to decide when you want to read them (on creating, before showing...) and store them (on close, ...). This depends on what you are saving/restoring. If you are going to use ready made components and want to save form position, then make sure to check how do they treat multiple monitors. If you are doing it your own way, you should take care of that yourself. For example, you might have a laptop and a big 22" monitor, and position of a form was saved while your big monitor was used. Later, if you open this form on laptop it might be displayed of screen so you can not see the form if this case is not handled properly.

Upvotes: 2

Related Questions