### Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

### Author Topic: How to access and set properties of Labels dynamically  (Read 1312 times)

#### andyf97

• New Member
• Posts: 20
##### How to access and set properties of Labels dynamically
« on: March 30, 2023, 01:48:35 am »
Hey up chaps.

I have two forms

On the main Form1 I have 6 Labels

On Form2 I have a button.

What I want to do is have a random number 1-6 that sets one of the label captions to that number, exist many methods online to generate the number but formatting the code to set the label is quite mind boggling, at least for me.

All I can think of is something like:  Form1 Label[R].caption = R

Press the button on form2
Random Number is generated and written to the caption of that label on form1.

its a little bit like an electronic dice

What would be the best approach to do that?

« Last Edit: March 30, 2023, 01:50:11 am by andyf97 »

#### jamie

• Hero Member
• Posts: 5364
##### Re: How to access and set properties of Labels dynamically
« Reply #1 on: March 30, 2023, 02:06:02 am »
you can build the name..

Label+IntTostr(Your_Random_Integer_Number);

and use TControl(FindComponent(Your_Random_Name)).Caption := IntTOStr(Your_Random_Number);

Something like that.

The only true wisdom is knowing you know nothing

#### andyf97

• New Member
• Posts: 20
##### Re: How to access and set properties of Labels dynamically
« Reply #2 on: March 30, 2023, 02:52:09 am »
That puzzles me, if it is known that the labels are on Form1 and we know the name of the one we want to change the caption for, why is it required to use the findcomponent?

you can build the name..

Label+IntTostr(Your_Random_Integer_Number);

and use TControl(FindComponent(Your_Random_Name)).Caption := IntTOStr(Your_Random_Number);

Something like that.

#### KodeZwerg

• Hero Member
• Posts: 1405
##### Re: How to access and set properties of Labels dynamically
« Reply #3 on: March 30, 2023, 10:04:26 am »
I would also use FindComponent but a little different than jamie has shown.
That puzzles me, if it is known that the labels are on Form1 and we know the name of the one we want to change the caption for, why is it required to use the findcomponent?
Because Form1 has "uses Unit2" (for Form2), but unit2 can not have "uses Unit1" (circulary references are forbid to have)

Attached a tested and working Demo.
« Last Edit: Tomorrow at 31:76:97 by KodeZwerg »

#### wp

• Hero Member
• Posts: 10882
##### Re: How to access and set properties of Labels dynamically
« Reply #4 on: March 30, 2023, 11:40:38 am »
Find my solution in the attachment. It introduces two ideas:
• I don't like to call FindComponent. Because I don't like to use the component's Name. And this is because the component name is not a useful concept when components are created at runtime since it always must be unique. There is much more flexibility when the component's Name is empty. It is clear that nothing is created at runtime in this project, but I think it is advantageous to simply forget about FindComponent. Rather than that, when there is a series of similar components it is much more appropriate to store them in an array and use the array name and the array indices to access the components. To do this, declare a variable "FLabels: array[1..6] of TLabel" in the form declaration, and in the form's OnCreate event store the labels in the array: "FLabels[1] := Label1; FLabels[2] := Label1", etc. Now each label can be accessed by the array index.
Code: Pascal  [Select][+][-]
1. type
2.   TForm1 = class(TForm)
3.     Label1: TLabel;
4.     Label2: TLabel;
5.     Label3: TLabel;
6.     Label4: TLabel;
7.     Label5: TLabel;
8.     Label6: TLabel;
9.     procedure FormCreate(Sender: TObject);
10.   private
11.     FLabels: array[1..6] of TLabel;
12.   end;
13.
14. procedure TForm1.FormCreate(Sender: TObject);
15. begin
16.   FLabels[1] := Label1;
17.   FLabels[2] := Label2;
18.   FLabels[3] := Label3;
19.   FLabels[4] := Label4;
20.   FLabels[5] := Label5;
21.   FLabels[6] := Label6;
22.   ClearLabels;
23. end;
• Using two forms - one to display the labels, and one to generate the random numbers - is a bit over-complicated; it would be easier if the random numbers were created on the same form that contains the labels. But anyway, maybe there is a reason for it that I don't know... The problem with two forms is that Form1 (the one with the labels) must contain code to show the other form (the one with the random generator), and the random generator form must know the label form to display the result. This is a circular reference situation which often leads to an uncompilable project. KodeZwerg's solution is nice but works only when the label form is the MainForm of the application. I think it would be possible to put the two units in the implementation's uses clause of the other unit - this is an allowed combination. But still - this is close to a blocking situation, and I prefer a different solution in which Form2 does not know Form1 at all. This can be achieved by introducing an event which Form2 fires whenever a new random number is available. Form1, on the other hand, which must know Form2 in order to show it, implements a handler for this event. Now unit2 must not have unit1 in its uses clause at all.
Code: Pascal  [Select][+][-]
1. // Form 2
2. type
3.   TRandomValueEvent = procedure(Sender: TObject; AValue: Integer) of object;
4.
5.   TForm2 = class(TForm)
6.     Button1: TButton;
7.     procedure Button1Click(Sender: TObject);
8.   private
9.     FOnRandomValue: TRandomValueEvent;
10.   public
11.     property OnRandomValue: TRandomValueEvent read FOnRandomValue write FOnRandomValue;
12.   end;
13.
14. procedure TForm2.Button1Click(Sender: TObject);
15. var
16.   value: Integer;
17. begin
18.   if Assigned(OnRandomValue) then
19.   begin
20.     value := Random(6) + 1;    // 1 ... 6
21.     OnRandomValue(Self, value);
22.   end;
23. end;
24.
25. //-------------------------------------------------------
26.
27. // Form1
28.
29. uses
30.   Unit2;
31.
32. procedure TForm1.Button1Click(Sender: TObject);
33. begin
34.   if Form2 = nil then
35.     Form2 := TForm2.Create(Application);
36.   Form2.OnRandomValue := @RandomValueHandler; // Assign the event handler to Form2
37.   Form2.Show;
38. end;
39.
40. procedure TForm1.RandomValueHandler(Sender: TObject; AValue: Integer);
41. begin
42.   GroupBox1.Caption := 'Received value: ' + IntToStr(AValue);
43.   ClearLabels;
44.   Labels[AValue].Caption := IntToStr(AValue);
45. end;

#### KodeZwerg

• Hero Member
• Posts: 1405
##### Re: How to access and set properties of Labels dynamically
« Reply #5 on: March 31, 2023, 01:55:40 am »
I don't like to call FindComponent. Because I don't like to use the component's Name.
Okies, removed FindComponent and Name to get valid labels
KodeZwerg's solution is nice but works only when the label form is the MainForm of the application.
I was doing how I thought that the final should do but of course I challenge your statement with an updated approach.
To make it work, name your destination labels as "xyzTarget123" on any form or use Tag (exemplary 666), my new code needs of course the forms name to work properly.
Code: Pascal  [Select][+][-]
1. procedure TForm2.Button1Click(Sender: TObject);
2. // remember, the search is case sensitive, you can use LowerCase() or UpperCase() to make it more generic working
3. // updated to work with Tag instead of Name for target labels
4. type
5.   TLabels = array of TLabel;
6. var
7.   x, y, z: Integer;
8.   arr: TLabels;
9. begin
10.   // initialize empty array
11.   arr := nil;
12.   SetLength(arr, 0);
13.   // iterate over root of application to find a specific form where we wanna change something
14.   for x := 0 to Pred(Application.ComponentCount) do
15.     // we searching for a form with a name of "Form1"
16.     if (Pos('Form1', Application.Components[x].Name) > 0) then
17.       // iterate over found form
18.       for y := 0 to Pred((Application.Components[x] as TForm).ComponentCount) do
19.         // exclude anything that is not a TLabel and find labels with a "Target" inside name or a matching Tag, just use one(!) way
20.         if (((Application.Components[x] as TForm).Components[y] is TLabel) and
21.             // compare against a value
22.             (((Application.Components[x] as TForm).Components[y] as TLabel).Tag = 666)) then
23.             // compare against a name
24. //            (Pos('Target', (Application.Components[x] as TForm).Components[y].Name) > 0)) then
25.           // we found something, add it to the internal array
26.           begin
27.             z := Length(arr);
28.             SetLength(arr, Succ(z));
29.             arr[z] := ((Application.Components[x] as TForm).Components[y] as TLabel);
30.           end;
31.   // when we found something
32.   if (Length(arr) > 0) then
33.     begin
34.       // pick one random and rename the caption to an integer
35.       x := Random(Length(arr));
36.       arr[x].Caption := IntToStr(Succ(x));
37.       // optional: we do not need the array anymore, kill it (it will get destroyed automagical when running out of scope)
38.       SetLength(arr, 0);
39.       arr := nil;
40.     end;
41. end;
« Last Edit: Tomorrow at 31:76:97 by KodeZwerg »

#### tetrastes

• Sr. Member
• Posts: 382
##### Re: How to access and set properties of Labels dynamically
« Reply #6 on: March 31, 2023, 10:31:25 am »
Oh, how many letters.

I have quite a few such cases in my programs, and I always do something like

Code: Pascal  [Select][+][-]
1. unit Unit2;
2. . . .
3.
4. implementation
5.
6. uses Unit1;
7. . . .
8.
9. procedure TForm2.Button1Click(Sender: TObject);
10. var
11.     i: integer;
12. begin
13.     i := Random(5) + 1;
14.     Form1.LabelsArray[i].Caption := IntToStr(i);
15. end;

and have no problems.

Though I understand, what wp wrote, but to create an event every time... May end up with too many EventHandlers.

#### andyf97

• New Member
• Posts: 20
##### Re: How to access and set properties of Labels dynamically
« Reply #7 on: March 31, 2023, 12:18:27 pm »
KodeZwerg, Thanks a lot for that and your time.

I tried it but it only lists the components on Form2

So I broke the idea down a bit to try and figure out what is going on, with this little bit of code it is also only listing components from Form2.

I really do not understand the hierarchy, is it like the object inspector where there are branches. Also what is this Pred() I could not find it anywhere.

Code: Pascal  [Select][+][-]
1. procedure TForm2.Button6Click(Sender: TObject);
2.   var
3.     i,x: Integer;
4.   begin
5.    for x := 0 to Pred(Application.ComponentCount) do
6.    if (Pos('Form1', Application.Components[x].Name) > 0) then  // we searching for a form with a name of "Form1"
7.    for i := 0 to ComponentCount-1 do
9. end;
10.
11.
12.
13.

Code: Pascal  [Select][+][-]
1. procedure TForm2.Button1Click(Sender: TObject);
2. // remember, the search is case sensitive, you can use LowerCase() or UpperCase() to make it more generic working
3. // updated to work with Tag instead of Name for target labels
4. type
5.   TLabels = array of TLabel;
6. var
7.   x, y, z: Integer;
8.   arr: TLabels;
9. begin
10.   // initialize empty array
11.   arr := nil;
12.   SetLength(arr, 0);
13.   // iterate over root of application to find a specific form where we wanna change something
14.   for x := 0 to Pred(Application.ComponentCount) do
15.     // we searching for a form with a name of "Form1"
16.     if (Pos('Form1', Application.Components[x].Name) > 0) then
17.       // iterate over found form
18.       for y := 0 to Pred((Application.Components[x] as TForm).ComponentCount) do
19.         // exclude anything that is not a TLabel and find labels with a "Target" inside name or a matching Tag, just use one(!) way
20.         if (((Application.Components[x] as TForm).Components[y] is TLabel) and
21.             // compare against a value
22.             (((Application.Components[x] as TForm).Components[y] as TLabel).Tag = 666)) then
23.             // compare against a name
24. //            (Pos('Target', (Application.Components[x] as TForm).Components[y].Name) > 0)) then
25.           // we found something, add it to the internal array
26.           begin
27.             z := Length(arr);
28.             SetLength(arr, Succ(z));
29.             arr[z] := ((Application.Components[x] as TForm).Components[y] as TLabel);
30.           end;
31.   // when we found something
32.   if (Length(arr) > 0) then
33.     begin
34.       // pick one random and rename the caption to an integer
35.       x := Random(Length(arr));
36.       arr[x].Caption := IntToStr(Succ(x));
37.       // optional: we do not need the array anymore, kill it (it will get destroyed automagical when running out of scope)
38.       SetLength(arr, 0);
39.       arr := nil;
40.     end;
41. end;

#### KodeZwerg

• Hero Member
• Posts: 1405
##### Re: How to access and set properties of Labels dynamically
« Reply #8 on: March 31, 2023, 12:30:48 pm »
and have no problems.
I do have a problem with your way.
Quote
Unit2.pas(8,9) Error: Circular unit reference between Unit2 and Unit1
« Last Edit: Tomorrow at 31:76:97 by KodeZwerg »

#### KodeZwerg

• Hero Member
• Posts: 1405
##### Re: How to access and set properties of Labels dynamically
« Reply #9 on: March 31, 2023, 12:39:21 pm »
I tried it but it only lists the components on Form2
I have no Idea about your projects source details so I attached a demo again that has on form2 a button to random change a "target/666" label from form1 or list everything that form1 has.
« Last Edit: March 31, 2023, 12:41:16 pm by KodeZwerg »
« Last Edit: Tomorrow at 31:76:97 by KodeZwerg »

#### KodeZwerg

• Hero Member
• Posts: 1405
##### Re: How to access and set properties of Labels dynamically
« Reply #10 on: March 31, 2023, 12:44:37 pm »
So I broke the idea down a bit to try and figure out what is going on, with this little bit of code it is also only listing components from Form2.
Code: Pascal  [Select][+][-]
1.    for i := 0 to ComponentCount-1 do // <<<<< ERROR, you use componentcount for current form and not the one that you searched
2.    Memo2.Lines.Add(Components[i].Name); // <<<<< ERROR, you use componentcount for current form and not the one that you searched
3.
So if you copy and paste, please do it correct
« Last Edit: Tomorrow at 31:76:97 by KodeZwerg »

#### wp

• Hero Member
• Posts: 10882
##### Re: How to access and set properties of Labels dynamically
« Reply #11 on: March 31, 2023, 12:45:00 pm »
No, there is no circular unit reference when both units are listed under implementation:
Code: Pascal  [Select][+][-]
1. // Unit 1
2. unit Unit1;
3. interface
4. ...
5. implementation
6. uses
7.   Unit2;
8. procedure TForm1.Button1Click(Sender: TObject);
9. begin
10.   Form2.Show;
11. end;
12. end.
13.
14. -------------------------------------------------------
15.
16. // Unit 2
17. unit Unit2;
18. interface
19. ...
20. implementation
21. uses
22.   Unit1;
23. ...
24. procedure TForm2.Button1Click(Sender: TObject);
25. var
26.     i: integer;
27. begin
28.     i := Random(5) + 1;
29.     Form1.LabelsArray[i].Caption := IntToStr(i);
30. end;
31. end.
32.

BUT: When the project gets bigger and bigger and more and more units are added the interdependence of the units becomes very complicated and there is a chance that such a solution will not compile any more. Also, you cannot test the project units independently - you always need Form1 if you want to test Form2 (and if Form1 uses other forms and units you will also need those - a huge mess!). Another issue is that unit2 compiles only when the instance of TForm1 is really named "Form1". Therefore, unless you have a very simple project you should always avoid such a situation.
« Last Edit: March 31, 2023, 12:52:23 pm by wp »

#### KodeZwerg

• Hero Member
• Posts: 1405
##### Re: How to access and set properties of Labels dynamically
« Reply #12 on: March 31, 2023, 12:52:55 pm »
Also what is this Pred() I could not find it anywhere.
Pred gives back the Value - 1.
Therefore, unless you have a very simple project you should always avoid such a situation.
I second that, to me it is very bad code design as it may lead to future bugs.
« Last Edit: Tomorrow at 31:76:97 by KodeZwerg »

#### GetMem

• Hero Member
• Posts: 4077
##### Re: How to access and set properties of Labels dynamically
« Reply #13 on: March 31, 2023, 12:54:16 pm »
@wp
Quote
No, there is no circular unit reference when both units are listed under implementation
IIRC one of the unit can be in the interface section if the other one is in the implementation section.

I would pass Form1 to Form2's constructor, then store the reference in a private variable. Also subscribing to an event(as in your demo) is a nice solution.

• Hero Member
• Posts: 1405