16

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?

duplode
  • 33,731
  • 7
  • 79
  • 150
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551

2 Answers2

28

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.

vcldeveloper
  • 7,399
  • 2
  • 33
  • 39
  • Thanks, that looks good. Now I will just wrap it in a loop and make it recursive and I'm set to go. – Mawg says reinstate Monica Jan 22 '13 at 02:41
  • 1
    Why setting Parent to nil before freeing a child component? Instead of the whole for loop, wouldn't `Component.DestroyComponents` do the trick just as well? – NGLN Nov 15 '13 at 19:21
  • 1
    Why is `LoadComponentFromFile()` adding the `Component` to the `Application`'s list of owned components? It should not be doing that at all. – Remy Lebeau Jun 09 '15 at 23:37
2

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.

avra
  • 3,690
  • 19
  • 19
  • +1 it's a low-runner, but I will be sure to take care of it (in the worst case, the user can edit or rename the save file) – Mawg says reinstate Monica Jan 24 '13 at 02:40
  • 1
    @MawgsaysreinstateMonica: Another option (which I use) is to make any setting that has a size/position on the desktop involved be stored within a section of the .INI file/Registry that contains the full size of the entire desktop, such as CONFIG.5760x2160 and CONFIG.1920x1080 - That way you can interchange the monitor setup as needed, and a configuration will be saved for each possible (used) actual desktop size. – HeartWare Sep 06 '22 at 09:31