Recent

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

andyf97

  • New Member
  • *
  • Posts: 21
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: 6090
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: 21
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: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
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 xm by KodeZwerg »

wp

  • Hero Member
  • *****
  • Posts: 11853
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: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
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 xm by KodeZwerg »

tetrastes

  • Sr. Member
  • ****
  • Posts: 473
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: 21
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
  8.    Memo2.Lines.Add(Components[i].Name);
  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: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
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 xm by KodeZwerg »

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
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 xm by KodeZwerg »

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
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 xm by KodeZwerg »

wp

  • Hero Member
  • *****
  • Posts: 11853
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: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
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 xm by KodeZwerg »

balazsszekely

  • Guest
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.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
Re: How to access and set properties of Labels dynamically
« Reply #14 on: March 31, 2023, 02:26:01 pm »
Another approach could be by using the Message system to have an event.
That way the "doing" is local.
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

 

TinyPortal © 2005-2018