Reputation: 40205
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
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
[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
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
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
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