Recent

Author Topic: How to use the contents of a string as a variable name  (Read 13332 times)

winni

  • Hero Member
  • *****
  • Posts: 3197
Re: How to use the contents of a string as a variable name
« Reply #15 on: August 15, 2021, 02:35:54 pm »
Hi!

You showed how to produce an error with variants.

But you did not show, how to get

Code: Pascal  [Select][+][-]
  1. Variant.FromName

to start working

Winni


avk

  • Hero Member
  • *****
  • Posts: 752
Re: How to use the contents of a string as a variable name
« Reply #16 on: August 15, 2021, 05:27:41 pm »
Mormot uses this approach for example in its TDocVariant.
In some minimal form, something similar can be found in my lgVarJson unit.
Anyway, this code
Code: Pascal  [Select][+][-]
  1. program test;
  2. {$mode objfpc}{$H+}
  3. uses
  4.   SysUtils, lgVarJson;
  5.  
  6. var
  7.   v: Variant;
  8.  
  9. begin
  10.   v := VarJsonCreate;
  11.   v.Wow := 'Wow';
  12.   v.My1001 := 1001;
  13.   v.Pi := Pi;
  14.   v.False := False;
  15.   v.AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything := 42;
  16.  
  17.   WriteLn(v.Wow);
  18.   WriteLn(v.My1001);
  19.   WriteLn(v.Pi);
  20.   WriteLn(v.False);
  21.   WriteLn(v.AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything);
  22. end.
  23.  
compiles and prints
Code: Text  [Select][+][-]
  1. Wow
  2. 1001
  3. 3,14159265358979
  4. false
  5. 42
  6.  

winni

  • Hero Member
  • *****
  • Posts: 3197
Re: How to use the contents of a string as a variable name
« Reply #17 on: August 15, 2021, 06:35:57 pm »
Thanx!

And nice that Douglas Adams has now its own constant ....

Winni


avk

  • Hero Member
  • *****
  • Posts: 752
Re: How to use the contents of a string as a variable name
« Reply #18 on: August 15, 2021, 08:29:07 pm »
Didn't this constant belong to him before? :)

dbannon

  • Hero Member
  • *****
  • Posts: 2750
    • tomboy-ng, a rewrite of the classic Tomboy
Re: How to use the contents of a string as a variable name
« Reply #19 on: August 16, 2021, 02:16:21 am »
Didn't this constant belong to him before? :)

It was not really his, I think it belonged to the mice.

Davo
Lazarus 2, Linux (and reluctantly Win10, OSX)
My Project - https://github.com/tomboy-notes/tomboy-ng and my github - https://github.com/davidbannon

Blade

  • Full Member
  • ***
  • Posts: 177
Re: How to use the contents of a string as a variable name
« Reply #20 on: August 16, 2021, 08:58:32 am »
Mormot uses this approach for example in its TDocVariant.
In some minimal form, something similar can be found in my lgVarJson unit.

Interesting, definitely will be playing with this. 

https://github.com/avk959/LGenerics
« Last Edit: August 16, 2021, 09:17:26 am by Blade »

PascalDragon

  • Hero Member
  • *****
  • Posts: 5418
  • Compiler Developer
Re: How to use the contents of a string as a variable name
« Reply #21 on: August 16, 2021, 08:58:50 am »
You showed how to produce an error with variants.

But you did not show, how to get

Code: Pascal  [Select][+][-]
  1. Variant.FromName

to start working

Because engkin already showed that earlier. I was merely addressing the point that from the compiler's PoV any identifier exists inside a variant.

Jiří Huňáček

  • New Member
  • *
  • Posts: 15
Re: How to use the contents of a string as a variable name
« Reply #22 on: August 18, 2021, 05:37:26 pm »
As marcov wrote, I will give a specific example

Code: Pascal  [Select][+][-]
  1. procedure ResetLabelCaptions;
  2. var
  3.   i: integer;
  4.   s: string;
  5.  
  6. begin
  7.   for i := 1 to 5 do
  8.   begin
  9.     s := 'Label' + IntToStr(i);
  10.     s.Caption := '';
  11.   end;
  12. end;

this of course doesn't work. I have about 30 labels in the application. I wanted to know if it is possible to make it possible to set it to an empty string in bulk, then fill in just some of them in the same way (I have the data from the database in an array) and I don't want to print it one by one.
Best regards / mit freundlichen Grüßen / s pozdravem
Jiří Huňáček (George)

Warfley

  • Hero Member
  • *****
  • Posts: 1486
Re: How to use the contents of a string as a variable name
« Reply #23 on: August 18, 2021, 06:03:21 pm »
this of course doesn't work. I have about 30 labels in the application. I wanted to know if it is possible to make it possible to set it to an empty string in bulk, then fill in just some of them in the same way (I have the data from the database in an array) and I don't want to print it one by one.

In this case all you need is a unified addressing mechanism. You can search for components by using FindChildControl in the parent.
But this can be quite slow as it uses linear search. Better to build a lookup table, e.g. by using an array:
Code: Pascal  [Select][+][-]
  1. // as field in your form
  2. DataLabels: Array[1..30] of TLabel;
  3. ...
  4. // in form create:
  5. for i:=1 to 30 do
  6.   DataLabels[i] := LabelParent.FindChildControl('Label' + i.ToString);
  7. ...
  8. // whenever you need to access a label by its number:
  9. DataLabels[num].Caption := ...
That said, FindChildControl is probably fast enough, considering that updating the UI takes quite a while itself. Compared to that the iteration through the controls should be rather fast

PS: You defenetly should give your labels a more meaningful name other then the default name (LabelXX), because otherwise you will soon lose sight about this in your code when you add more labels that are not part of that group
« Last Edit: August 18, 2021, 06:07:49 pm by Warfley »

winni

  • Hero Member
  • *****
  • Posts: 3197
Re: How to use the contents of a string as a variable name
« Reply #24 on: August 18, 2021, 06:31:20 pm »
Hi!

Another solution.
Requirement is, that the labels are named label1,label2,....label30

We use a set of byte so you can adress Labels up to label255

Code: Pascal  [Select][+][-]
  1. type
  2.    SetOfByte = Set Of Byte;
  3.  
  4.    TForm1 = class(TForm)
  5.                         ....
  6.                         Label1: TLabel;
  7.                         Label2: TLabel;
  8.                         ....
  9.                         procedure FillLabel(BSet : SetOfByte; s : string);
  10.                         ....
  11.                          ...
  12.  
  13.  
  14. procedure TForm1.FillLabel (BSet : SetOfByte; s : string);
  15. var i,k : integer;
  16. begin
  17. for i := 0 to ComponentCount -1 do
  18.    begin
  19.    if (Components[i] is TLabel) then
  20.        begin
  21.        for k := 0 to 255 do
  22.           begin
  23.           if k in Bset then
  24.              begin
  25.              if Components[i].name = 'Label'+IntToStr(k) then
  26.                  begin
  27.                  Tlabel(Components[i]).caption := s;
  28.                  break; // k
  29.                  end;
  30.             end;
  31.         end;
  32.    end;
  33.  end;
  34. end;
  35.  


Now you can clear all labels with

Code: Pascal  [Select][+][-]
  1. FillLabel ([0..255], '');
  2.  

And you can fill selected Labels with

Code: Pascal  [Select][+][-]
  1. FillLabel([1,3,7..10], 'A new caption');
  2.  

Winni
« Last Edit: August 18, 2021, 06:34:21 pm by winni »

damieiro

  • Full Member
  • ***
  • Posts: 200
Re: How to use the contents of a string as a variable name
« Reply #25 on: August 29, 2021, 03:28:24 pm »
As marcov wrote, I will give a specific example

Code: Pascal  [Select][+][-]
  1. procedure ResetLabelCaptions;
  2. var
  3.   i: integer;
  4.   s: string;
  5.  
  6. begin
  7.   for i := 1 to 5 do
  8.   begin
  9.     s := 'Label' + IntToStr(i);
  10.     s.Caption := '';
  11.   end;
  12. end;

this of course doesn't work. I have about 30 labels in the application. I wanted to know if it is possible to make it possible to set it to an empty string in bulk, then fill in just some of them in the same way (I have the data from the database in an array) and I don't want to print it one by one.

Well, i have been reading all the posts, and i have the sense of killing flies with cannons...
I would follow markov way of thinking.

Why in the hell would you will need it?


Renaming labels seems bad idea with a visual editor and things can be done with meaningful names.
And you will have an overloading of some dictionary on all things if needed

It seems that the case is that the program will take some text archive or similar to make a "custom" form from file or making some separation between interface designer and code implementation, but with a Lazarus IDE it should not be needed. Other options of use seems some kind of multi-languaje support..

What are this feature needed for?
I think this would make a better aproach

« Last Edit: August 29, 2021, 06:18:16 pm by damieiro »

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11272
  • FPC developer.
Re: How to use the contents of a string as a variable name
« Reply #26 on: August 29, 2021, 05:03:08 pm »
To load series of components into an array I use:

Code: Pascal  [Select][+][-]
  1. procedure loadarray (root:tcomponent;var x : TControlDynArray;basename:string;i1,i2:integer);
  2. var l,i : integer;
  3. begin
  4.  l:=i2-i1+1;
  5.  setlength(x,l);
  6.  for i := i1 to i2 do
  7.    begin
  8.      x[i-i1]:=tcontrol(root.FindComponent(basename+inttostr(i)));
  9.    end;
  10. end;

and then use this for e.g.

Code: Pascal  [Select][+][-]
  1.   labs : array of tlabel;
  2.  
  3. loadarray (self,tcontroldynarray(labs),'Edit',5,14);
  4.  
  5.  

to load edit5 .. edit14 into an dynamic array with 10 (0..9) elements, which can then be easily FOR()ed.

bataff

  • Newbie
  • Posts: 4
Re: How to use the contents of a string as a variable name
« Reply #27 on: November 18, 2023, 07:06:16 pm »
Hello,
The subject is interesting.
Without the use of a unit or variants, i found two leads :
1. with the enumerated type, because it is possible to rely each argument with a longint (reading by the function ord()) and have the string's name of a argument by a use of typeInfo() (and other things).
  But the possibilities by this way is very limited, the longint is only intended to enumerate the arguments of the enum variable ... (too hard/impossible to manipulate the variable).
2. By writing the variable name yourself in the program, or at least the initial name. Indeed, as has already been said in this thread, the compiled program only needs the location of the variable. If, for example, another variable is created with the same address (by 'absolute'), the program cannot know which of the two is at that location.
Here's an idea for that (only for byte's type, to start with):
- we create a small record like this :
Code: Pascal  [Select][+][-]
  1. type bytename = record
  2.        v : byte;
  3.        name : string;
  4.  end;
- we create a 'bytename''s variable and a byte's variable with the same adresse :
Code: Pascal  [Select][+][-]
  1. var b : bytename;
  2.     v : byte absolute b;
v and b.v will be identical (same address) and b.name will be the place provided for the variable name.
In this way, you put the name of the variable immediately after it in memory, and then know where the name is if you know the address of the variable (the only thing known about a variable, apart from its content).
Looking down this path, here's an example of a program that uses several variables with names to be established at the beginning:
Code: Pascal  [Select][+][-]
  1. program test;
  2. Const maxcharname = 10; //maximal number of characters in a variable's name.
  3. type bytename = record
  4.        v : byte;
  5.        name : string[maxcharname];
  6.      end;
  7. var vn : array[1..5] of bytename;
  8.   alpha : byte absolute vn[1];
  9.   beta : byte absolute vn[2];
  10.   c : byte absolute vn[3];
  11.   d : byte absolute vn[4];
  12.   e : byte absolute vn[5];
  13.  
  14. procedure writebytename(var b : byte;s : string); //save the name s of a byte's variable just after this one in memory.
  15. var i : byte;
  16. begin
  17.   if length(s)>0 then begin
  18.     pbyte(@b+1)^:=length(s); //write the length of string s in vn[].name[0] (pbyte(@b)^ corresponding to the value of the byte's variable just before).
  19.     for i:=1 to length(s) do if i<=maxcharname then pchar(@b+i+1)^:=s[i]; //copy the characters of s in name[1], name[2], ...
  20.   end;                                                                                                                                    //note : functionning but maybe this code it's too long.
  21. end;
  22.  
  23. function readbytename(var b : byte) : string; //return the name of a byte's variable (if saved before by writebytename or else).
  24. var i : byte;
  25. begin
  26.   readbytename:='';
  27.   for i:=1 to pbyte(@b+1)^ do if i<=maxcharname then readbytename:=readbytename+pchar(@b+i+1)^; //functionning but maybe this code it's too long.
  28. end;
  29.  
  30. BEGIN
  31.   alpha:=8; writebytename(alpha,'alpha'); beta:=21; writebytename(beta,'beta'); writebytename(c,'c'); writebytename(d,'d'); writebytename(e,'e');
  32.   writeln(readbytename(alpha),' = ',alpha); //output : "alpha = 8"
  33.   writeln(readbytename(beta),' = ',beta); //output : "beta = 21"
  34. END.

Well, there is no need of units but it is more long than simply creating variables, but ... it's fonctionning.
« Last Edit: November 18, 2023, 08:21:49 pm by bataff »

bataff

  • Newbie
  • Posts: 4
Re: How to use the contents of a string as a variable name
« Reply #28 on: November 18, 2023, 10:01:39 pm »
Hmm, sorry, I got the subject backwards ...
Instead of returning the value of a variable from a string, my program returns a string from the variable.

However, I've come up with another idea using enumerated types:
- Create an enum with the variable names as arguments.
- Create an array of pointers to the variables.
- We use the GetEnumValue function (from the typinfo unit), which returns the position of the variable name in the enum, then the array of pointers to find the value of the variable.

Here's a simple example I did quickly (still using bytes)
Code: Pascal  [Select][+][-]
  1. program test2;
  2. uses typinfo;
  3. const maxvar = 50;      //Total number of variables used.
  4. type en = (alphaE,betaE,cE,dE,eE); //variable's names as enum's arguments, with a 'E' in the end in order to distinguish them from the true variables.
  5. var varname : array[1..maxvar] of pbyte;
  6.     alpha,beta,c,d,e : byte;
  7.    
  8. function readvaluebyte(s : string) : byte; //returns the value of the byte variable whose name is s.
  9. var pti : PTypeInfo;
  10. begin
  11.   pti:=TypeInfo(en);            //note : I don't know if this is how we use a PTypeInfo variable in GetEnumValue, but it works.
  12.   readvaluebyte:=varname[GetEnumValue(pti,s+'E')+1]^;
  13. end;
  14.  
  15. BEGIN
  16.   alpha:=20; beta:=15; c:=8; d:=12; e:=15;
  17.   varname[1]:=@alpha; varname[2]:=@beta; varname[3]:=@c; varname[4]:=@d ; varname[5]:=@e; //Initialization of the array of pointers.
  18.   writeln('beta = ',readvaluebyte('beta')); //output : 'beta = 15'
  19. END.
Well, I admit I used a unit, but at least it's simpler!
« Last Edit: November 18, 2023, 11:02:42 pm by bataff »

 

TinyPortal © 2005-2018