Help with key=value assignments lazarus / 21-General ---------------------------------------------------------------------- cpicanco · 2023-11-18 14:11 Hi everyone, Background Take a look at this implementation: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TTrial.SetTrialData(ATrialData: TTrialData);begin  FData := ATrialData;  FIStimuli := GetIStimuli;  FIStimuli.Load(FData.Parameters, Self);  if Assigned(FData.Parameters) then begin    with FData.Parameters, TrialKeys do begin      FLimitedHoldTimer.Interval := StrToIntDef(Values[LimitedHoldKey], 0);      FInterTrialInterval := StrToIntDef(Values[InterTrialIntervalKey], 0);      FConsequenceInterval := StrToIntDef(Values[ConsequenceIntervalKey], 0);      FHasConsequence := StrToBoolDef(Values[HasConsequenceKey], True);    end;    CreateStartersIfRequired(FData.Parameters);  end;end; Some points: 1) FData.Parameters is a TStringList used solenely by its Values[] property. Actual key=value pairs are read from an ini file into the list once. 2) Of course, it is necessary to do an explict convertion of each parameter before class field assignments. Here, TrialKeys is a record with constants thought to make my life safer (not easier) when calling each parameter key. 3) Finally, FData.Parameters is passed to many other classes that have a "Load(Parameters : TStringList)" method. Notice that each class receives everything in the string list, but will take just what it needs. Question I am looking for ideas on how to implement once a parameter (class implementation and reading from inifile with necessary convertions) and automagically get its class instance assigments done. For example, would be great to have something like this inside the "Load method" of a "TParametricObject" base class: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i for Parameter in AData.Parameters do begin   Self.Parameters[Parameter.Key].Value := Parameter.Value;end;  Do you have any suggestions to achieve this (or something similar/equivalent). ---------------------------------------------------------------------- paweld · 2023-11-18 14:45 Hi,    the following come to mind TFPGMap from fgl unit.  Something like this:Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit ParameterU; {$mode ObjFPC}{$H+} interface uses  Classes, SysUtils, fgl; type   { TParameterList }   TParameterList = class (specialize TFPGMap)  private    procedure SetParameter(aKey, AValue: String);    function GetParameter(aKey: String): String;  public    constructor Create;    property Parameter[aKey: String]: String read GetParameter write SetParameter;  end; implementation { TParameterList } procedure TParameterList.SetParameter(aKey, AValue: String);var  idx: Integer;begin  idx := IndexOf(aKey);  if idx >= 0 then    Data[idx] := AValue  else    Add(aKey, AValue);end; function TParameterList.GetParameter(aKey: String): String;var  idx: Integer;begin  Result := '';  idx := IndexOf(aKey);  if idx >= 0 then    Result := Data[idx]  else    raise Exception.Create('Key "' + aKey + '" not exists');end; constructor TParameterList.Create;begin  inherited Create;  Duplicates := dupIgnore; //or dupAccept or dupError;  Sorted := True;end; end. exec example:Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program Project1; {$mode objfpc}{$H+} uses  Classes, SysUtils, ParameterU; var  paramlist: TParameterList;  i: Integer; begin  paramlist := TParameterList.Create;  for i := 0 to 100 do    paramlist.Add('Key' + IntToStr(i), 'Value ' + IntToStr(i));  for i := 0 to paramlist.Count - 1 do    WriteLn(paramlist.Keys[i], ' = ', paramlist.Data[i]);  WriteLn('--------');  WriteLn('old value: ', paramlist.Parameter['Key10']);  paramlist.Parameter['Key10'] := '1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/';  WriteLn('new value: ', paramlist.Parameter['Key10']);  WriteLn('--------');  try    WriteLn(paramlist.Parameter['???']);  except    on E: Exception do    WriteLn(E.Message);  end;  paramlist.Parameter['???'] := '!!!!!!!!!!!!!!';  WriteLn(paramlist.Parameter['???']);  paramlist.Free;  ReadLn;end. ---------------------------------------------------------------------- jamie · 2023-11-18 14:59 How about simply sticking a STATIC variable within the class you want to multiply. Inside the class Var MyList:TStringList; STATIC etc  You need to load this up once somewhere but if all works well, you all of your instances should see the same string list. ---------------------------------------------------------------------- cpicanco · 2023-11-18 15:12 Thanks jamie and paweld. Quote from: jamie on November 18, 2023, 03:59:02 pmHow about simply sticking a STATIC variable within the class you want to multiply. Inside the class Var MyList:TStringList; STATIC etc  You need to load this up once somewhere but if all works well, you all of your instances should see the same string list. The program creates only one TStringlist at a time. I am just passing the reference from one class (TTrial) and to all composite classes (TStimuli, TStimulus, and so on...). So, there is only one already. Quote from: paweld on November 18, 2023, 03:45:43 pmHi,    the following come to mind TFPGMap from fgl unit. paweld, it is a really great suggestion to clean up the code a little bit. But I am afraid it does not solve the class assigment part of the problem. I am looking for some solution to assign each parameter, no matter how data type it was converted, to its corresponding class instance field. ---------------------------------------------------------------------- jamie · 2023-11-18 15:24 Hmm  Maybe you want to implement the "Assign" call ? For that, you need to base your class from TPersistent and implement the AssignTo method. By doing this once, you can create a class of the same and then do the Assign where it will copy the values over to the new. ---------------------------------------------------------------------- cpicanco · 2023-11-18 15:33 Hi Jamie, I really don't need copies. The problem here is how to create the first instance from parameters (read from the inifile). 1) read inifile and build string=string/key=value parameters with TStringList (or TFPGMap) 2) do the convertion of each parameter (now we have key=boolean, key=integer .. key=Type) 3) assign each parameter to its corresponding field in the class inside a loop I am missing steps between 2 and 3. ---------------------------------------------------------------------- jamie · 2023-11-18 15:45 It appears what you maybe after is RTTI where you can search the item using the RTTI interface and have it assigned the value from the stringlist/IniFile.  For that to happen, all items need to be published and after that you can run a loop through the RTTI to find the item and assign it, the same way the system works now when loading up your properties from the resource file.  All this is run time operations, is that what you are after? ---------------------------------------------------------------------- cpicanco · 2023-11-18 15:48 Yes, run time! I will take a look at the RTTI. ---------------------------------------------------------------------- cpicanco · 2023-11-18 16:51 https://forum.lazarus.freepascal.org/index.php?topic=52446.0 So, what is the current status of RTTI? Does it supports getting the list of fields (or properties) from a class for assignment purposes? ---------------------------------------------------------------------- dalfy · 2023-11-18 17:52 Quote from: cpicanco on November 18, 2023, 04:12:43 pmI am looking for some solution to assign each parameter, no matter how data type it was converted, to its corresponding class instance field. TParameterList = class (specialize TFPGMap) ---------------------------------------------------------------------- jamie · 2023-11-18 19:40 You can take a look at this. this was a test project experimenting with some Component to text items that already exists in the FCL stuff. ---------------------------------------------------------------------- cpicanco · 2023-11-18 20:15 Thanks jamie, it is a really nice solution for persistence of existing components. However some sort of extra work is required for custom components, as It fails here: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls,  StdCtrls,Lresources; type   { TMyComponent }   TMyComponent = class(TComponent)  private    FMyBool: Boolean;    function GetMyBool: Boolean;    procedure SetMyBool(AValue: Boolean);  published    property MyBool : Boolean read GetMyBool write SetMyBool;  end;   { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    Memo1: TMemo;    Panel1: TPanel;    procedure Button1Click(Sender: TObject);  private   public    Procedure findClassEvent(Read:TReader;Const aClassName:String;Var ComponentClass:TComponentClass);  end; var  Form1: TForm1;  TheStream:TMemoryStream; implementation {$R *.lfm}  { TMyComponent }  function TMyComponent.GetMyBool: Boolean; begin   Result := FMyBool; end;  procedure TMyComponent.SetMyBool(AValue: Boolean); begin   FMyBool := AValue; end; { TForm1 } Procedure Tform1.findClassEvent(Read:TReader;Const aClassName:String;Var ComponentClass:TComponentClass); Begin    ComponentClass := TComponentClass(FindClass(aClassName)); end; procedure TForm1.Button1Click(Sender: TObject);var  E,E2:TMyComponent;  R:TComponent;begin  //Include the LResources unit.  while Panel1.ComponentCount <>0 do Panel1.Components[0].free;  RegisterClass(TMyComponent);   E:= TMyComponent.Create(Panel1);  E.Name := 'MyComponent1';  E.MyBool := True;   E2:= TMyComponent.Create(Panel1);  E2.Name := 'MyComponent2';   E.MyBool := False;   Sleep(1000);  TheStream := TMemoryStream.Create;   For R in Panel1 do  writeComponentAsTextToStream(TheStream,R);  FreeAndNil(E); FreeAndNil(E2);  { Lets make believe a few days have gone by and we now want to reload it}  TheStream.Position := 0;  While TheStream.Position < TheStream.Size do Begin   E := Nil;   ReadCompone ntFromTextStream(TheStream,TCOmponent(E),@FindClassEvent,Panel1,Nil);  end; {Here,   E.MyBool = False, Am I doing something wrong?}  TheStream.Position := 0;  Memo1.Lines.LoadFromStream(TheStream);//Show the contents.  FreeAndNil(TheStream); end; end. ---------------------------------------------------------------------- cpicanco · 2023-11-18 20:28 Lets assume that this read/write from LResources works, I can see that it would require me to rewrite a lot of stuff to make this work here. 1) I am reading parameters from csv into a TIniFile (not into objects directly). 2) I am reading my Parameters list from each csv line (converted to a section in the ini file); 3) This parameter may affect many classes, as I mentioned earlier, so, persistence of single objects is not what I am looking for (but you found an excellent solution for a different application) Do you agree that this great suggestion does not work for this scenario? ---------------------------------------------------------------------- cpicanco · 2023-11-18 20:46 Quote from: dalfy on November 18, 2023, 06:52:55 pmQuote from: cpicanco on November 18, 2023, 04:12:43 pmI am looking for some solution to assign each parameter, no matter how data type it was converted, to its corresponding class instance field. TParameterList = class (specialize TFPGMap) I will give it a try. Maybe it can be used in conjunction with RTTI somehow. ---------------------------------------------------------------------- jamie · 2023-11-18 21:32 I don't know why you don't simply hard code some fields in a record and get defineds from your INIFILE startup read cycle? These fields can be linked via the compiler so your classes can simply read the field it needs, it does not have to be a text based search etc.   Properties in the publish arena are all text based as for their names etc., so don't expect any performance from your code if a lot of activity is taking place.  What I would do is this:     Create a Record with all your fields that is used only in memory and in that record add the management code that loads and save those values to and from the INIFile.  This would be the case of using an Advanced Record and thus you need to ensure the feature is on.  You can pass this record via a reference to any of the class instances and from that point on, those classes can make a direct access to any of those fields, or you can do a simple copy of the complete record to a local record within each instance of the class, which will chew up memory of you have a lot of instances.   Its up to you but when you do the IniFile read and save you can specify the text name fields there and use the hard card fields in the record as their values.   What I do in cases like that I use CONSTANT names for those fields so they only get allocated once in compile form and you simply reference them. This way you can always change the name of the single item and all will reflect to it.  If you need and example of that I can show you something but the advanced Class is a perfect candidate for such a task since you can put all of the management code within it. ---------------------------------------------------------------------- jamie · 2023-11-18 22:07 Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+}{$modeSwitch AdvancedRecords}interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,inifiles; type PValueRecord = ^TValueRecord; TValueRecord = Record   Private   FFile:TIniFile;   FKeyOne:Integer;   FKeyTwo:Integer;   Public   Procedure LoadRecord(AFileName:String);   Public   Property KeyOne:Integer Read FKeyOne;   Property KeyTwo:Integer Read FKeyTwo; end;   { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    procedure Button1Click(Sender: TObject);  private   public   end; TTest = Class   FValueRecord:PvalueRecord;   Constructor Create(AValueRecord:PValueRecord); end; Const  SectionName ='This Section';  KeyOneName  ='This is Key One';var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject);Var  T:Ttest; //Class  R:TValueRecord;begin  R.LoadRecord('xxxxx.ini');  T := T.Create(@R);  //.....  T.Free; //Do clean ups etc. // S.SaveRecord(.....);end; Constructor Ttest.Create(AValueRecord:PValueRecord);Begin  fValueRecord := AValueRecord; //Now you have access to this same record without copying it.  //...End;Procedure TValueRecord.LoadRecord(AFileName:String);Begin  //  //do your INIT FILE loading here.  fFile := TIniFile.Create(AFileName);  fFile.ReadInteger(SectionName ,KeyOneName, 1000);   //define all your fields and changes etc.  FFile.Free;end; end.   Look at this example, it's just enough to give you an idea. ---------------------------------------------------------------------- cpicanco · 2023-11-19 02:28 Quote from: jamie on November 18, 2023, 11:07:30 pmI don't know why you don't simply hard code some fields in a record and get defineds from your INIFILE startup read cycle? 1) In the beginning, I was hard coding stuff. It is becoming harder and harder to maintain. What do you think about the attached project?