Link to home
Start Free TrialLog in
Avatar of ST3VO
ST3VOFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Saving and Loading application data

Hi all,

I have an application in which I have lots of Tedits and Tmemo components where I add data to.
I need to save all the data to a file where I can there load it back but at this point I don't want to use a database or registry to add all this.
How could I do this please?
thx

st3vo
Avatar of Emmanuel PASQUIER
Emmanuel PASQUIER
Flag of France image

Let's say you have 2 forms Form1 and Form2, each of them having 2 TEdit and 2 TMemo (which happens to have the same names)

Use these procedures below (call from Form1) :

IniLoadComponnents( 'MyFile.Dat' , 'Form1', [Edit1,Edit2,Memo1,Memo2]);
IniLoadComponnents( 'MyFile.Dat' , 'Form2', [Form2.Edit1,Form2.Edit2,Form2.Memo1,Form2.Memo2]);
...
IniSaveComponnents( 'MyFile.Dat' , 'Form1', [Edit1,Edit2,Memo1,Memo2]);
IniSaveComponnents( 'MyFile.Dat' , 'Form2', [Form2.Edit1,Form2.Edit2,Form2.Memo1,Form2.Memo2]);

Uses IniFiles;
 
procedure IniLoadComponnents(FileName,Section:String;List:Array of TObject);
Var
 Ini:TIniFile;
begin
 Ini:=TIniFile.Create(FileName);
 for i:=Low(List) to High(List) do if Assigned(List[i]) Then
  begin
   if List[i] Is TEdit Then With TEdit(List[i]) do Text:=Ini.ReadString(Section,Name,Text);
   if List[i] Is TMemo Then With TMemo(List[i]) do Text:=Ini.ReadString(Section,Name,Text);
  end;
 Ini.Free; 
end; 
  
procedure IniSaveComponnents(FileName,Section:String;List:Array of TObject);
Var
 Ini:TIniFile;
begin
 Ini:=TIniFile.Create(FileName);
 for i:=Low(List) to High(List) do if Assigned(List[i]) Then
  begin
   if List[i] Is TEdit Then With TEdit(List[i]) do Ini.WriteString(Section,Name,Text);
   if List[i] Is TMemo Then With TMemo(List[i]) do Ini.WriteString(Section,Name,Text);
  end;
 Ini.Free; 
end; 
  

Open in new window

Avatar of ST3VO

ASKER

Sorry, but you got my question wrong.
Yes, I've got lots of Memos and Edits which I need to save but I need to save what's written on them and them loaded back into place.

Know what I mean?
Yes, this is what it does.
Try it, and tell me what's different from what you want
Do you mean you'd like to save them dynamically without having to enumerate them in an array ?

Then try this :

procedure IniSaveCOmponnents(FileName:String; Form:TForm);
Var
 Ini:TIniFile;
begin
 for i:=0 to Form.ComponentCount-1 do 
  begin
   if Form.Components[i] Is TEdit Then With TEdit(Form.Components[i]) do Ini.WriteString(Form.Name,Name,Text);
   if Form.Components[i] Is TMemo Then With TMemo (Form.Components[i]) do Ini.WriteString(Form.Name,Name,Text);
  end;
 Ini.Free; 
end; 

Open in new window

@epasquier,
As long as the Text that your code is saving doesn't have any CRLF's in it, and is not terribly long, your approach will work.  However, if either of those conditions is violated, then it will fail.
@ST3VO,
You might try writing the data out to a set of text files, appropriately named with, for instance, a combination of the Form Name and TMemo Field Name.  Thus, if the form is frmMain and the TMemo is memJobDescription , then your file name might be frmMain_memJobDescription.txt.  This would allow you to use the SaveToFile and LoadFromFile methods of the TMemo.Lines.  
For Example:
frmMain.memJobDescription.lines.SaveToFile('frmMain_memJobDescription.txt');

frmMain.memJobDescription.lines.LoadFromFile('frmMain_memJobDescription.txt');
Here:
http://delphi.about.com/od/beginners/l/aa092899.htm

You can modify it a little bit to suit your needs.
> As long as the Text that your code is saving doesn't have any CRLF's in it, and is not terribly long,
> your approach will work.  However, if either of those conditions is violated, then it will fail.
True, I use this approach usually to save/load application parameters, and TMemo is not part of my full unit support. We should have a Search/Replace for CR/LF with something else to make it work nicely as is.
Other file for Memos is the right approach I think (for a quick solution with flat files)
For TEdit, that is OK. I doubt that a TEdit.Text would be more than 4000 char long (which is ~ the limit, I think) and cannot have CR/LF.

Here is the modified code for correct TMemo support
procedure IniSaveComponnents(FileName:String; Form:TForm);
Var
 Ini:TIniFile;
 Path:String;
begin
 Ini:=TIniFile.Create(FileName);
 Path:=InclueTrailingBackslash(ExtractFilePath(FileName));
 for i:=0 to Form.ComponentCount-1 do 
  begin
   if Form.Components[i] Is TEdit Then With TEdit(Form.Components[i]) do Ini.WriteString(Form.Name,Name,Text);
   if Form.Components[i] Is TMemo Then With TMemo (Form.Components[i]) do 
    Lines.SaveToFile(Path+Name+'.MMO');
  end;
 Ini.Free; 
end; 
 
procedure IniLoadComponnents(FileName:String; Form:TForm);
Var
 Ini:TIniFile;
 Path:String;
begin
 Ini:=TIniFile.Create(FileName);
 Path:=InclueTrailingBackslash(ExtractFilePath(FileName));
 for i:=0 to Form.ComponentCount-1 do 
  begin
   if Form.Components[i] Is TEdit Then With TEdit(Form.Components[i]) do 
    Text:=Ini.ReadString(Section,Name,Text);
   if Form.Components[i] Is TMemo Then With TMemo (Form.Components[i]) do 
    Lines.LoadFromFile(Path+Section+'.'+Name+'.MMO');
  end;
 Ini.Free; 
end; 

Open in new window

How to save/load contents of edits to file :
http://www.festra.com/eng/snip02.htm
Usually I use a function to create my file just in case something like :

Function justomakesurefilexists:Boolean;  
Var oznakaDat:integer;
Begin
if not FileExists('senad.TXT') then
begin
oznakaDat:=FileCreate('senad.txt');  
FileClose(oznakaDat);
end;
Result:=FileExists('senad.TXT');
end;
@senad :
Yeah, sure, we could do that the old way. But it's not very elegant and will be a problem when one component is added or remove, we could not reload the older version of the file. Which is why I used the Ini file approach in the first place, it can find the data associated to a component by its section (form name) and name, and has support for default value (the value of the component @design).
Ini files have "almost" everything we need. This can be extended with label, checkbox, radio buttons, form states & captions etc...
As Diver states once (I believe he was), one "problem" is that users could modify it. I kind of think of it as a functionality, because between me and the users there is a distributor who likes to be able to play, even if he does rarely. And the user never goes there. In fact, if a user wants to create problems, he does not need to edit a config file, he just have to delete it, either he can read it or not.
Guys, while go to all that work when you have the LoadFrom/SaveToFile at your finger tips?!?!?! ;-)
Of course, the step I left for the student, so to speak, was the creation of the file name based upon the form name and the TMemo name and, if needed, the ensuring of the file's non-existence or the desire to over-write the file.  (However, I would assume, since this is a work-file, that the file should be over written if it exists.)
All the efforts to manipulate the INI files, etc., are a bit of over kill, IMHO, given the original request.  However, to the extent that it is desired to use an INI, I have a unit I can provide that encapsulates the basics of dealing with an INI file in a slightly more convenient manner than using the raw INIFile unit. ;-)
>> All the efforts to manipulate the INI files, etc., are a bit of over kill

Well, how do you want to save multiple TEdit.Text values without an IniFile ? I mean, there are a lot of solutions, managing some self-structured indexed file, but why looking for anything else than Ini as long as TEdit is concerned ?
While I will concede that the INI file is quite handy for the TEdits (among other things), the TMemos having their pathed filenames inserted into the INI file so that you have to a) read the INI file for the pathed filename and then b) read the file just seems a bit of overkill.  Since the apparent requirement is for a workfile, I would tend to assume (and, yes, I know how to divide that word ;-) that the storage is intended to be a) local and b) temporary.  Therefore, stoing the TMemos to an unpathed filename would preserve the data for the current user in a relatively known location from which it could be retrieved without having to go through the INI file manipulations.
Well, how do you want to save multiple TEdit.Text values without an IniFile ?
For that matter, the TEdits could be written to a common file from a TSTringList (also using the LoadFrom/SaveToFile process.  since each TEdit has to have a unique name that does not contain embedded spaces, using the name/value pairs of a TStringList would work well.  In fact, a simple routine to walk the form to insert/update the TStringList could be written and then another that walks the TStringList (after it is read back in) to fill the TEdits would address the entire problem.
In my INIFile handler, one of the procedures I provide reads an INI section into a TStringList for, basically, that same purpose.  There is also a corresponding write procedure that outputs a TStringList as a section in an INI file.  The only difference would be that the process would involve a text file and it would be simpler.
> the TMemos having their pathed filenames inserted into the INI file so that you have to
> a) read the INI file for the pathed filename and then b) read the file just seems a bit of overkill.

This is not what it does. Check again post ID 25635563

>> For that matter, the TEdits could be written to a common file from a TSTringList
>> since each TEdit has to have a unique name that does not contain embedded spaces, using the name/value pairs of a TStringList would work well.  
Which is exactly what is done in an INI file, with same pb and limitations, without the section feature which can be handy to regroup key/values per forms for example.

>> In fact, a simple routine to walk the form to insert/update the TStringList could be written and then another that walks the TStringList (after it is read back in) to fill the TEdits would address the entire problem.
Just rewriting IniFiles unit with lesser functionalities
>Just rewriting IniFiles unit with lesser functionalities
Actually, no, because the INIFiles unit requires the INIFile object and then the specification of the Section as well as defaults on a read, and several other things.  Because an INI file has a specific format, it requires more in the way of manipulation to write to it and read from it.  The procedures to walk the form finding TEdit fields in order to add them to the TStringList or populate them from the TStringList don't require any smarts to speak of.  The TStringList SaveTo/LoadFromfFile methods are predefined and will save whatever is in the TStringList or load it with whatever is in the file.  So, the developer only has to create a proc that either clears the TStringList and then walks the form, filling the TstringList based on TEdit names and contents before saving the file or one that clears the list, fills it , and then finds and populates the TEdits based on the data read in.  Since it is possible to not have any idea how many or even if there are TEdit fields on the form and still accomplish the save and load, there is no real requirement for any smarts in the routine.
Similarly, a basic pair of SaveMyTMemoData and FetchMyTMemoData procedures can be passed the TForm object to be walked looking for TMemo fields and either writing them out or reading them in as requested.  Once again, no need to know if there are any TMemo fields, so no need for the routine to have much in the way of smarts.
Both sets of routines are quick and dirty but would provide the workfile storage requested.
Just because you can build a sohpisticated solution doesn't mean that one is needed. ;-)
Avatar of ST3VO

ASKER

Thanks all...I'll start checking out the samples and will come back asap.
Avatar of ST3VO

ASKER

OK, Lots of code examples but I can't get any to work. I get errors on everyone of them. The first one from epasquier looks good although I haven't been able to test it as I get an error trying to save.

Any chance on getting unit and dfm so I can test it properly please?
By the way, here is a quick and dirty Unit that I think will handle the saving and loading of the TEdits and TMemos. ;-)
I plan on fleshing it out a bt more and doing a bit more documentation because I think it may be useful for other purposes. ;-)
Please, note, I have doulbe zipped it to get past the file upload filters.  When you unzip the outer wrapper, you will need to remove the ".txt" fromthe ending and unzip again. ;-)

TWGWorkFileUtilities.zip.zip
Avatar of ST3VO

ASKER

Thanks but how do I use it? and can I add other components of just Edits and Memos?

thx

The procedures are public (FULLY public) so add the unit to your Uses and then call the procedures with the TForm object as a parameter and it should work.  Haven't had time to really test it because, as I said, it is a Q&D that I plan to flesh out.  Read the code and it should be fairly self-explanatory . . . assuming some basic knowledge of Delphi and Pascal. ;-)
The trick to adding other Components is you have to know what it is you are going to save from them and retrieve to them.  Since the basic concept involves text file storage, you will want to be saving and retrieving text information.
I am thinking that this could be kindof useful for testing purposes where you may want to fill in a bunch of fields but not want to have to do so repeatedly by hand . . . add a Save and a Load function that is enabled during testing and, pof, you have your solution.  It scould also be handy for allowing a user to provide a set of files for you to recreate a problem (to some degree).
Anyway, like I said, I plan to flesh it out more for my own purposes. ;-)
> I haven't been able to test it as I get an error trying to save.
What error ? are you using the last post # 25635563 ? how do you use it ?
@Diver
> Please, note, I have doulbe zipped it to get past the file upload filters.  

Do you know you can upload files without restrictions with a secondary file server :
http://www.ee-stuff.com/Expert/Upload/upload.php
Avatar of ST3VO

ASKER

I tried to save it like this:

IniSaveComponnents( 'MyFile.Dat' , 'Form1', [Edit1,Memo1]);

I get 'Error:   Unable to write to MyFile.dat

 
It needed debugging in a few lines

a complete working project is on the way, here is the corrected code
Uses IniFiles;
 
procedure IniSaveComponnents(FileName:String; Form:TForm);
Var
 Ini:TIniFile;
 Path:String;
 i:integer;
begin
 Ini:=TIniFile.Create(FileName);
 Path:=IncludeTrailingBackslash(ExtractFilePath(FileName));
 for i:=0 to Form.ComponentCount-1 do 
  begin
   if Form.Components[i] Is TEdit Then With TEdit(Form.Components[i]) do Ini.WriteString(Form.Name,Name,Text);
   if Form.Components[i] Is TMemo Then With TMemo (Form.Components[i]) do 
    Lines.SaveToFile(Path+Form.Name+'.'+Name+'.MMO');
  end;
 Ini.Free;
end; 
 
procedure IniLoadComponnents(FileName:String; Form:TForm);
Var
 Ini:TIniFile;
 Path,MemoFile:String;
 i:integer;
begin
 Ini:=TIniFile.Create(FileName);
 Path:=IncludeTrailingBackslash(ExtractFilePath(FileName));
 for i:=0 to Form.ComponentCount-1 do
  begin
   if Form.Components[i] Is TEdit Then With TEdit(Form.Components[i]) do
    Text:=Ini.ReadString(Form.Name,Name,Text);
   if Form.Components[i] Is TMemo Then With TMemo (Form.Components[i]) do
    begin
     MemoFile:=Path+Form.Name+'.'+Name+'.MMO';
     if FileExists(MemoFile) then Lines.LoadFromFile(MemoFile);
    end;
  end;
 Ini.Free; 
end;

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Emmanuel PASQUIER
Emmanuel PASQUIER
Flag of France image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ah, I see that you where using the first version of it. Well, basically only the loop changes.

If you prefer specifying which controls you want to save/load instead of getting them all (which I prefer also), just correct the latest version of the working project with the array loop as in the first post.

I think I'll add support of these different approach to my general-purpose IniOptions unit. I'll post it there when I'll have time. In the meantime, these lightweight versions should be good enough for your current need.
>>epasquier
try finally ?
not necessary ?

try your procs with passing a nil as a form

Avatar of ST3VO

ASKER

Thanks a million
Avatar of ST3VO

ASKER

Thanks to all of you for your time and examples. The last sample by epasquier works perfect for me so I'm assigning my points to him. Thanks all!
Avatar of ST3VO

ASKER

p.s: 8080_Diver, I couldn't get your's working but I'm sure it's just my luck of knowledge and not your code. Thanks for your time.
Hello Geert ! I wondered when you would come in on this subject ;o)
try finally ? Yes, in the absolute it would be good.

Now it's not a complicated function to use, the error your are talking about is the kind that should earn kicks in the @?? of the developer as it has no chance to work ever (where try finaly are more useful to catch run-time errors that are contextual not due to stupid programming) and last, this is just a free sample to quickly get the idea of a working solution, not a fool-proof commercial component.

besides, it would be better to test that parameter for nil value and then quit with a ShowMessage('Sorry, the developer had not had his 3 cups of coffee before using this function.');
instead of waiting that an error arrives inevitably
Avatar of ST3VO

ASKER

No worries...works great now :o)  

thx again

yeah, i see ... basic proc
i try to build the simple procs memory leak proof too ...

you won't get a next question trying to find the memory leak in the future

just take this sample (gives division by zero)

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Top := 0 + Height / fDelta;  // I know it's a silly sample
end;

and in the level higher (maybe in .dpr):

Application.CreateForm(TForm1, Form1);
IniLoadComponents('forms.ini', Form1);

procedure IniLoadComponnents(FileName:String; Form:TForm);
var
 Ini: TIniFile;
 Path, MemoFile: String;
 i: integer;
begin
  Ini := TIniFile.Create(FileName);
  try 
    Path := IncludeTrailingBackslash(ExtractFilePath(FileName));
    for i := 0 to Form.ComponentCount - 1 do
    begin
      if Form.Components[i] is TEdit Then 
        with TEdit(Form.Components[i]) do
          Text := Ini.ReadString(Form.Name, Name, Text);
      if Form.Components[i] is TMemo then 
        with TMemo(Form.Components[i]) do
        begin
          MemoFile := Format(%s%s.%s.MMO', [Path, Form.Name, Name]);
          if FileExists(MemoFile) then 
            Lines.LoadFromFile(MemoFile);
        end;
    end;
  finally
    Ini.Free; 
  end;
end;

Open in new window

forgot a quote

MemoFile := Format('%s%s.%s.MMO', [Path, Form.Name, Name]);
Let's bury the hatchet there.
You are right, as always. But if every sample code we post here has to be full of try.. finally it will quickly become unreadable for beginners that only wants tips for a particular problem.
Otherwise, the next step will be to post only fool-proof samples with a complete usage documentation.

Still, If I really wanted to do that, I'd rather have this, much better than an access violation message
procedure IniLoadComponnents(FileName:String; Form:TForm);
var
...
begin
 if not Assigned(Form) Then Raise Exception.Create('IniLoadComponnents called with nil form parameter');
...
end;

Open in new window

@ST3VO,
not your code. Thanks for your time.
 For the time, your welcome; however, since I slammed it together and didn't have time to test it, it could well have been the code.
@epasquier,
Do you know you can upload files without restrictions with a secondary file server :
[i]http://www.ee-stuff.com/Expert/Upload/upload.php[/i]
Frankly, no I didn't know but, silly me, I tried to use the "Attach File" process which, IMHO, should be made to work, if by no other means then by using the same link you provided.
I tend to have a simplistic outlook on web sites . . . either their features work or they don't.  I don't expect to have to go digging around on the web site for ways around supposed features that fail.  
>> Frankly, no I didn't know but, silly me, I tried to use the "Attach File" process

Yeah, I too tried that with the same disappointment and the same conclusion : it would be great if they could fix their so-called protection feature that gets in our way when uploading files that they don't recognise (all delphi files, what a chance for us). I've posted all delphi extensions and they told me that it would be taken into account the next time they change this "Attach File" feature. In the meantime, I guess we have no choice.
@epasquier,
In the meantime, I guess we have no choice.
Well, actually, you  (or I) have shown that there are at lesat 2 options. ;-)
By the way, I had a chance not only to debug the TWGWorkFielUtilities.pas unit that I had previously provided (and, yes, it was my code ;-) but I also added a couple of procedures that save and fetch TLabel contents and I added some documentation.  I have uploaded a file (again, double compressed with the first compression having an added ".txt" file type ;-) that has that utility and my AboutBox form.  
The AboutBox has a DoubleClick event handler that saves the various AboutBox TLabel Captions, TEdit contents (of which there ar none), and TMemo contents.  I did have to cast the form name as a TForm object when I used the Fetch procedures on the form I initially tried it on, though.
I got to thinking about it and the FetchTLabels  procedure could be handy for Localizations, so it has a default LanguageTag of ".txt." and there are some notes about passing different tags for the save or fetch so that, with very little effort, you can load different TLabel captions for different languages.
@epasquier,
The TLabel additions were why I really thought this technique could be useful. ;-)
Oops, forgot the file. :-/
TWGSamples.zip.zip