Link to home
Start Free TrialLog in
Avatar of pr2501
pr2501

asked on

Reading from ini

Am using code from proposition of Geert_Gruwez from the post:
https://www.experts-exchange.com/questions/26588395/Ini-file-complication.html Now i need also code to modificate my code below to read data about TLabel.
Sorry i have tray but i can't do it by myself.

procedure TForm1.FormCreate(Sender: TObject);
var
  cmp: TShape;
  i: integer;
  Fname:string;

begin  //1
  FShapeNumber := 0;
 with TINIFile.Create(ExtractFilePath(Application.Exename) + 'yourini.ini')do  begin //2
      try //3
       caption:= ReadString(Self.Name, 'Caption', Caption);
       LoadBackground( ReadString('_global_', 'bitmapfile', '' ) );
       pathstrBitmapFile:=  ReadString('_global_', 'bitmapfile', '' );
       n := ReadInteger(Self.Name, 'Shapes', 0);
       for i := 1 to n do
       begin  //4
       // if you use TShape as class, you only need to cast once
         cmp := TShape(FindComponent('Shape' + IntToStr(i)));
         if cmp = nil then
         begin //5
          cmp := TShape.Create(Self);
          cmp.Name := 'Shape' + IntToStr(I);
          cmp.Parent := Self;
          cmp.onMouseDown := ShapeMouseDown;
          cmp.onMouseMove := ShapeMouseMove;
          cmp.onMouseUp := ShapeMouseUp;
          cmp.Tag := FShapeNumber;
          inc(FShapeNumber);
          cmp.Brush.Color := TColor(ReadInteger(Self.Name, cmp.Name + ' Brush Color', integer(clGreen)));
          cmp.Width := ReadInteger(Self.Name, cmp.Name + ' Width', cmp.Width);
          cmp.Height := ReadInteger(Self.Name, cmp.Name + ' Height', cmp.Height);
          cmp.Top := ReadInteger(Self.Name, cmp.Name + ' Top', cmp.Top);
          cmp.Left := ReadInteger(Self.Name, cmp.Name + ' Left', cmp.Left);
          cmp.hint :=readString(Self.Name, cmp.Name + ' Hint',TShape(cmp).Hint);
          cmp.showhint :=readBool(Self.Name, cmp.Name + ' ShowHint',TShape(cmp).Showhint);
            end; //5
          end;  // 4
       finally
       free;
       end; //3
     Application.Showhint := true;
  end;  //2

end;  //1

Open in new window

Avatar of Emmanuel PASQUIER
Emmanuel PASQUIER
Flag of France image

You know, if you indented properly you would not need those //1 , //2 etc comments...

Come on, that is not much more work to write visually nice code... And that is the first step to produce nice code in all aspects
* never put begin at the end of lines : always return to next line and ident
* put every end in the same column of the begin that corresponds
* same goes with try / finally / end , case / end

that is the absolute minimum. after, there are some habits that differ from one developer to another. I prefer indenting one space at a time only (but before and after a begin, so all my end are indented 2 spaces from each other. I found that a good balance between space used and readability

now, about your problem, what are the TLabel properties you want to save/read in your ini file ?
Avatar of jimyX
jimyX

Just added the TLabel default properties that you might be using, and also just worked on the shape code to get this:



procedure TForm1.FormCreate(Sender: TObject);
var
  cmp: TShape;
  i: integer;
  Fname:string;

begin  //1
  FShapeNumber := 0;
  
 with TINIFile.Create(ExtractFilePath(Application.Exename) + 'yourini.ini')do  begin //2
      try //3
       caption:= ReadString(Self.Name, 'Caption', Caption);
       LoadBackground( ReadString('_global_', 'bitmapfile', '' ) );
       pathstrBitmapFile:=  ReadString('_global_', 'bitmapfile', '' );

       LabelNumber := 0;
       n := ReadInteger(Self.Name, 'Labels', 0);
       for i := 1 to n do
       begin
         cmp := TLabel(FindComponent('Label' + IntToStr(i)));
         if cmp = nil then
           begin
             cmp := TLabel.Create(Self);
             cmp.Name := 'Label' + IntToStr(I);
             cmp.Parent := Self;
             cmp.onClick := LabelClick;
             inc(LabelNumber);
             cmp.Caption := ReadString(Self.Name, cmp.Name + ' Caption', TLabel(cmp).Caption));
             cmp.Font.Color := TColor(ReadInteger(Self.Name, cmp.Name + ' Font Color', integer(clWindowText)));
             cmp.Font.name := ReadString(Self.Name, cmp.Name + ' Font Name', TLabel(cmp).Font.Name));
             cmp.Font.Size := ReadInteger(Self.Name, cmp.Name + ' Font Size', TLabel(cmp).Font.Size);
             cmp.Top := ReadInteger(Self.Name, cmp.Name + ' Top', cmp.Top);
             cmp.Left := ReadInteger(Self.Name, cmp.Name + ' Left', cmp.Left);
             cmp.AutSize := ReadBool(Self.Name, cmp.Name + ' AutoSize', TLabel(cmp).AutoSize);
             cmp.Transparent := ReadBool(Self.Name, cmp.Name + ' Transparent', TLabel(cmp).Transparent);
             cmp.WordWrap := ReadBool(Self.Name, cmp.Name + ' WordWrap', TLabel(cmp).WordWrap);
           end;
       end;

       n := ReadInteger(Self.Name, 'Shapes', 0);
       for i := 1 to n do
       begin  //4
       // if you use TShape as class, you only need to cast once
         cmp := TShape(FindComponent('Shape' + IntToStr(i)));
         if cmp = nil then
         begin //5
          cmp := TShape.Create(Self);
          cmp.Name := 'Shape' + IntToStr(I);
          cmp.Parent := Self;
          cmp.onMouseDown := ShapeMouseDown;
          cmp.onMouseMove := ShapeMouseMove;
          cmp.onMouseUp := ShapeMouseUp;
          cmp.Tag := FShapeNumber;
          inc(FShapeNumber);
          cmp.Brush.Color := TColor(ReadInteger(Self.Name, cmp.Name + ' Brush Color', integer(clGreen)));
          cmp.Width := ReadInteger(Self.Name, cmp.Name + ' Width', cmp.Width);
          cmp.Height := ReadInteger(Self.Name, cmp.Name + ' Height', cmp.Height);
          cmp.Top := ReadInteger(Self.Name, cmp.Name + ' Top', cmp.Top);
          cmp.Left := ReadInteger(Self.Name, cmp.Name + ' Left', cmp.Left);
          cmp.hint :=readString(Self.Name, cmp.Name + ' Hint',TShape(cmp).Hint);
          cmp.showhint :=readBool(Self.Name, cmp.Name + ' ShowHint',TShape(cmp).Showhint);
            end; //5
          end;  // 4
       finally
       free;
       end; //3
     Application.Showhint := true;
  end;  //2

end;  //1

Open in new window

Sorry, forgot to change from the "cmp" taht was for the TShape, it should be different, I changed to "Lbel":

procedure TForm1.FormCreate(Sender: TObject);
var
  cmp: TShape;
  Lbel : TLabel;
  i: integer;
  Fname:string;

begin  //1
  FShapeNumber := 0;
  
 with TINIFile.Create(ExtractFilePath(Application.Exename) + 'yourini.ini')do  begin //2
      try //3
       caption:= ReadString(Self.Name, 'Caption', Caption);
       LoadBackground( ReadString('_global_', 'bitmapfile', '' ) );
       pathstrBitmapFile:=  ReadString('_global_', 'bitmapfile', '' );

       LabelNumber := 0;
       n := ReadInteger(Self.Name, 'Labels', 0);
       for i := 1 to n do
       begin
         Lbel := TLabel(FindComponent('Label' + IntToStr(i)));
         if Lbel = nil then
           begin
             Lbel := TLabel.Create(Self);
             Lbel.Name := 'Label' + IntToStr(I);
             Lbel.Parent := Self;
             Lbel.onClick := LabelClick;
             inc(LabelNumber);
             Lbel.Caption := ReadString(Self.Name, Lbel.Name + ' Caption', TLabel(Lbel).Caption));
             Lbel.Font.Color := TColor(ReadInteger(Self.Name, Lbel.Name + ' Font Color', integer(clWindowText)));
             Lbel.Font.name := ReadString(Self.Name, Lbel.Name + ' Font Name', TLabel(Lbel).Font.Name));
             Lbel.Font.Size := ReadInteger(Self.Name, Lbel.Name + ' Font Size', TLabel(Lbel).Font.Size);
             Lbel.Top := ReadInteger(Self.Name, Lbel.Name + ' Top', Lbel.Top);
             Lbel.Left := ReadInteger(Self.Name, Lbel.Name + ' Left', Lbel.Left);
             Lbel.AutSize := ReadBool(Self.Name, Lbel.Name + ' AutoSize', TLabel(Lbel).AutoSize);
             Lbel.Transparent := ReadBool(Self.Name, Lbel.Name + ' Transparent', TLabel(Lbel).Transparent);
             Lbel.WordWrap := ReadBool(Self.Name, Lbel.Name + ' WordWrap', TLabel(Lbel).WordWrap);
           end;
       end;

       n := ReadInteger(Self.Name, 'Shapes', 0);
       for i := 1 to n do
       begin  //4
       // if you use TShape as class, you only need to cast once
         cmp := TShape(FindComponent('Shape' + IntToStr(i)));
         if cmp = nil then
         begin //5
          cmp := TShape.Create(Self);
          cmp.Name := 'Shape' + IntToStr(I);
          cmp.Parent := Self;
          cmp.onMouseDown := ShapeMouseDown;
          cmp.onMouseMove := ShapeMouseMove;
          cmp.onMouseUp := ShapeMouseUp;
          cmp.Tag := FShapeNumber;
          inc(FShapeNumber);
          cmp.Brush.Color := TColor(ReadInteger(Self.Name, cmp.Name + ' Brush Color', integer(clGreen)));
          cmp.Width := ReadInteger(Self.Name, cmp.Name + ' Width', cmp.Width);
          cmp.Height := ReadInteger(Self.Name, cmp.Name + ' Height', cmp.Height);
          cmp.Top := ReadInteger(Self.Name, cmp.Name + ' Top', cmp.Top);
          cmp.Left := ReadInteger(Self.Name, cmp.Name + ' Left', cmp.Left);
          cmp.hint :=readString(Self.Name, cmp.Name + ' Hint',TShape(cmp).Hint);
          cmp.showhint :=readBool(Self.Name, cmp.Name + ' ShowHint',TShape(cmp).Showhint);
            end; //5
          end;  // 4
       finally
       free;
       end; //3
     Application.Showhint := true;
  end;  //2

end;  //1

Open in new window

here is your code 'reformated' and adding some TLabel support
procedure TForm1.FormCreate(Sender: TObject);
var
 shp: TShape;
 lbl: TLabel
 i: integer;
 Fname:string;
begin
 FShapeNumber := 0;
 FLabelNumber := 0; // declare that
 with TINIFile.Create(ExtractFilePath(Application.Exename) + 'yourini.ini') do
  begin 
   try
    caption:= ReadString(Self.Name, 'Caption', Caption);
    LoadBackground( ReadString('_global_', 'bitmapfile', '' ) );
    pathstrBitmapFile:=  ReadString('_global_', 'bitmapfile', '' );
    n := ReadInteger(Self.Name, 'Shapes', 0);
    for i := 1 to n do
     begin
      // if you use TShape as class, you only need to cast once
      shp := TShape(FindComponent('Shape' + IntToStr(i)));
      if shp = nil then
       begin
        shp := TShape.Create(Self);
        shp.Name := 'Shape' + IntToStr(I);
        shp.Parent := Self;
        shp.onMouseDown := ShapeMouseDown;
        shp.onMouseMove := ShapeMouseMove;
        shp.onMouseUp := ShapeMouseUp;
        shp.Tag := FShapeNumber;
        inc(FShapeNumber);
        shp.Brush.Color := TColor(ReadInteger(Self.Name, shp.Name + ' Brush Color', integer(clGreen)));
        shp.Width := ReadInteger(Self.Name, shp.Name + ' Width', shp.Width);
        shp.Height := ReadInteger(Self.Name, shp.Name + ' Height', shp.Height);
        shp.Top := ReadInteger(Self.Name, shp.Name + ' Top', shp.Top);
        shp.Left := ReadInteger(Self.Name, shp.Name + ' Left', shp.Left);
        shp.hint :=readString(Self.Name, shp.Name + ' Hint', shp.Hint);
        shp.showhint :=readBool(Self.Name, shp.Name + ' ShowHint', shp.Showhint);
       end;
     end;
    n := ReadInteger(Self.Name, 'Labels', 0);
    for i := 1 to n do
     begin
      // if you use TLabel as class, you only need to cast once
      lbl := TLabel(FindComponent('Label' + IntToStr(i)));
      if lbl = nil then
       begin
        lbl := TLabel.Create(Self);
        lbl.Name := 'Label' + IntToStr(I);
        lbl.Parent := Self;
        lbl.Tag := FLabelNumber;
        inc(FLabelNumber);
        lbl.Font.Color := TColor(ReadInteger(Self.Name, lbl.Name + ' Font Color', integer(clBlack)));
        lbl.Top := ReadInteger(Self.Name, lbl.Name + ' Top', lbl.Top);
        lbl.Left := ReadInteger(Self.Name, lbl.Name + ' Left', lbl.Left);
        lbl.Caption :=readString(Self.Name, lbl.Name + ' Caption',lbl.Caption);
       end;
     end;
   finally
    free;
   end;
   Application.Showhint := true;
  end;
end;

Open in new window

Is the some special reason why it has to be in an INI file?

Filestreams would be much easier, since they already know how to save and load TComponents (and decendants) already.
  like;             SaveComponent('c:\label1.dat', Label1);
and              LoadComponent('c:\label1.dat', Label1);
 

procedure SaveComponent(AFilename:String; Comp:TComponent);
var fs:TFilestream;
begin
  fs:=TFilestream.create(AFilename,fmCreate);
  fs.WriteComponent(Comp);
  FreeAndNil(fs);
end;

procedure LoadComponent(AFilename:String; var Comp:TComponent);
var fs:TFilestream;
begin
  fs:=TFilestream.create(AFilename,fmOpenRead);
  comp := fs.ReadComponent;
  FreeAndNil(fs);
end;

Open in new window

Avatar of pr2501

ASKER

comp := fs.ReadComponent;

[Pascal Error] openmap.pas(663): E2035 Not enough actual parameters
Avatar of pr2501

ASKER

Sorry am still sliping.
change ;  comp := fs.ReadComponent;
to;                fs.ReadComponent(comp);
Use loadcomponent below;
var tc:TButton;
...
  tc := TButton.Create(Form1);   /// Form1 is current form
  LoadComponent('c:\temp\button.bin', tcomponent(tc), tform(Form1));
....
 
initialization

      RegisterClasses([TButton, TEdit, TListBox, TMemo, TForm,TLabel]);

procedure LoadComponent(AFilename:String; var Comp:TComponent;var AForm:TForm);
var fs:TFilestream; nc : TComponent;
begin
  fs:=TFilestream.create(AFilename,fmOpenRead);
  try
    fs.ReadComponent(comp);
    if Assigned(AForm) then
    begin
      if comp is TControl then
        Aform.InsertControl(tcontrol(comp))
      else AForm.InsertComponent(comp);
    end;
  finally
    FreeAndNil(fs);
  end;
end;

Open in new window

Maybe a string version of the save as documented in TStream.ReadComponent;
This ComponentToStringProc creates a DFM like text that is easy to manipulate and read. The demo program attached does a good job of educating what is possible.
Adding a simply a
Memo1.Lines.SavetoFile('c:\myfile.txt')    and     Memo1.Lines.LoadFromFile('c:\myfile.txt');
could be all you need.?
NOTE : You could use a TStringList instead of a Memo1

function ComponentToStringProc(Component: TComponent): string;
var
  BinStream:TMemoryStream;
  StrStream: TStringStream;
  s: string;
begin
  BinStream := TMemoryStream.Create;
  try
    StrStream := TStringStream.Create(s);
    try
      BinStream.WriteComponent(Component);
      BinStream.Seek(0, soFromBeginning);
      ObjectBinaryToText(BinStream, StrStream);
      StrStream.Seek(0, soFromBeginning);
      Result:= StrStream.DataString;
    finally
      StrStream.Free;
    end;
  finally
    BinStream.Free
  end;
end;

function StringToComponentProc(Value: string): TComponent;
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      Result:= BinStream.ReadComponent(nil);
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;

Open in new window

Project1.zip
So what you would end up is something ; (Add the last two code bits together)

That would save and load DFM like files (not completely tested)

procedure SaveComponent(AFilename:String: AComp:TComponent);
var sl : TStringlist;
begin
  sl := TStringlist.create;
  try
    sl.Text := ComponentToStringProc(AComp) ;
    sl.SaveToFile(aFilename);
  finally 
    freeandnil(sl);
  end;
end;

procedure SaveComponent(AFilename:String: var AComp:TComponent; AForm:TForm);
var sl : TStringlist;
begin
  sl := TStringlist.create;
  try
    sl.LoadFromFile(aFilename);
    AComp := StringToComponentProc(sl.text);
    if Assigned(AForm) then
    begin
      if Acomp is TControl then
       Aform.InsertControl(tcontrol(Acomp))
      else AForm.InsertComponent(Acomp);
    end;
  finally 
    freeandnil(sl);
  end;
end; 

Open in new window

Here's a unit DFMStorage;
only two calls

procedure LoadComponent(AFilename:String; var AComp:TComponent; AForm:TForm);
procedure SaveComponent(AFilename:String; AComp:TComponent);

DFMStorage.pas
Further simplified I found that the AComp:Tcomponent parameter is not really needed;
So I overloaded it; it is not required unless you want to pass it in and get a reference to it and change something;
procedure LoadComponent(AFilename:String; AForm:TForm); overload;
procedure LoadComponent(AFilename:String; AForm:TForm;var AComp:TComponent );overload;
procedure SaveComponent(AFilename:String; AComp:TComponent);

Update attached unit;

DFMStorage.pas
One of the reasons you have a problem reading back the information is because you dont store it properly
Your ini file does not really distinguish b/n labels and shapes

As I suggested in your previous question you should have different sections for sections for shapes and labels

eg
[SHAPES]
chape1.width= xxx

[LABELS]
label1.top = yyy

Then you don't need to keep searching for the components when loading from the INI.
Load a whole section into a stringlist and process the string list

And for me, an even better solution would be to use XML. Set properties of a node to correspond to the properties of your control and everything suddenly becomes so simple
Avatar of pr2501

ASKER

Thank you for you accurate assistance. It is that i had no time yesterday to study it. I'm not able to work on level like this, because programing with Delphi is not my job. I'm doing this for innovation in our factory. So pleas understand if am not giving expected fed back. Am still using code from Geert_Gruwez, but am stucked because i have problem which i have not recognized before with writing data into ini.
So i have spent many time on the problem and i believe that am near to resolution that am in one way refusing to start from the beginning even if the resolution would be much better.
 
Thank you
using the attached pascal file DFMStorage; (add to Uses) you can reduce the code to one line using;
SaveComponents andLoadComponents

Replacing the current FormClose and FormCreate with below code;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  DFMStorage.SaveComponents(
      ExtractFilePath(Application.Exename) + 'yourini.ini',
      Form1,[TLabel, TShape]); 
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin  
  DFMStorage.LoadComponents(
      ExtractFilePath(Application.Exename) + 'yourini.ini',
      Form1,[TLabel, TShape]); 
end;

Open in new window

DFMStorage.pas
Avatar of pr2501

ASKER

briangochnauer

your code is OK but if you lock at my code there are many other propreties which a have to store and i need also option of creating of ini file.
Then I guess it would be more like code below;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  with TINIFile.Create(ExtractFilePath(Application.Exename) +'yourini.ini')do  
  try
    WritedString(Self.Name, 'Caption', Caption);
    WriteString('_global_', 'bitmapfile', pathstrBitmapFile );
  finally
    free; 
  end;
  DFMStorage.SaveComponents(
      ExtractFilePath(Application.Exename) + self.name+'.fdm',
      Form1,[TLabel, TShape]); 
end;

procedure TForm1.FormCreate(Sender: TObject);
begin  
  with TINIFile.Create(ExtractFilePath(Application.Exename) + 'yourini.ini')do  
  try
    caption:= ReadString(Self.Name, 'Caption', Caption);
    LoadBackground( ReadString('_global_', 'bitmapfile', '' ) );
    pathstrBitmapFile:=  ReadString('_global_', 'bitmapfile', '' );
  finally
    free;
  end;
  DFMStorage.LoadComponents(
      ExtractFilePath(Application.Exename) +self.name+'.fdm',      Form1,[TLabel, TShape]); 
end;

Open in new window

Avatar of pr2501

ASKER

thank you
This should be the resolution.
Can you help me resolve next:
First chance exception at $7C812AFB. Exception class EFOpenError with message 'Cannot open file "C:\IG\delphi\final7\final7\Form1.fdm". The system cannot find the file specified'. Process Forma.exe (5668)

(yesterday when i have try Your code i had no error,  i have no odea what happened)

////////////////////////77
procedure LoadComponents(AFilename:String; AForm:TForm; AClasses: array of TPersistentClass);
var sl,tsl : TStringlist;
    line :string;
    j : integer;
   tc : TComponent;

begin
  sl := TStringlist.create;
  tsl := TStringlist.create;
  sl.LoadFromFile(Afilename);

  for j := 0 to sl.Count-1 do

Open in new window

This (attached file) will fix that error.
DFMStorage.pas
Avatar of pr2501

ASKER

Yes, now there are no error now.

But  restarting of app i lose labels and shapes.

What can be problem now?
Avatar of pr2501

ASKER



I try to resolve it by changening  " 'self.name+'.fdm' "  to " 'yourinishapelabel.ini' " and now i may see data into ini file, so what must i change to read it also, . Or what is the reason  why there is no shape and label in my app aftrer restarting of it.

ini file:
object shape1: TShape
  Left = 10
  Top = 30
  Width = 10
  Height = 10
  Hint = 'xv'
  Brush.Color = clGreen
end

object label1: TLabel
  Left = 10
  Top = 10
  Width = 12
  Height = 13
  Caption = 'xv'
end

 
With seeing you complete app; I can only guess why it is not working. My test app works great.

But it may be; the name of the object can only exist once (1 time) so if it loading an object and finds there is an object with that name already;  ('shape1') then that object will be ignored; the same for 'label1'

Labels (any object) don't have to be named label1 or labelx (where x is a number) they can be named with any amount of characters.
ASKER CERTIFIED SOLUTION
Avatar of briangochnauer
briangochnauer
Flag of United States of America 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
Avatar of pr2501

ASKER

thank you