Recent

Author Topic: Dynamically change button properties with loops  (Read 5530 times)

douglas.cast

  • Guest
Dynamically change button properties with loops
« on: November 24, 2017, 02:58:34 am »
Hi everyone.

I need to create a loop procedure that changes the Font.Styles property of some buttons (3 groups with 5 buttons each).

This is the code that I have until now:

Code: Pascal  [Select][+][-]
  1. procedure TfrmMain.boldButtons(btnName:TButton);
  2. var
  3.   btnNameID   :    String; //Holds the String name of the button like: C, D, J
  4.   counterBtn  :    Integer; //used for mat
  5. begin
  6.   btnNameID   := Copy(btnName.Name,1,1);
  7.  
  8.   counterBtn:=1;
  9.  
  10.   while counterBtn <> 5 do begin
  11.    {
  12.     Here is what I need to figure out, the btnNameID and the counterBtn will "call" the button name that I already have,
  13.     like B1 or C1 or L5 and them clean up the Font.Style to assing to the right button in the end of the code
  14.     }
  15.     btnNameID+IntToStr(counterBtn).Font.Style=[];
  16.     //It should be something like: C1.Font.Style=[];
  17.     counterBtn:=counterBtn+1;
  18.   end;
  19.   btnName.Font.Style:= [fsBold, fsItalic];
  20. end;    
  21.  

Any ideas are welcome

Josh

  • Hero Member
  • *****
  • Posts: 1270
Re: Dynamically change button properties with loops
« Reply #1 on: November 24, 2017, 05:43:03 am »
maybe findcomponent would work

Code: [Select]
if findcomponent(btnNameID+IntToStr(counterBtn))=nil then showmessage('Button :'+btnNameID+IntToStr(counterBtn)+' does not exist'))
else tbutton(findcomponent(btnNameID+IntToStr(counterBtn))).font.style:=[]
« Last Edit: November 24, 2017, 05:45:21 am by josh »
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Dynamically change button properties with loops
« Reply #2 on: November 24, 2017, 05:47:42 am »
Code: Pascal  [Select][+][-]
  1. procedure TfrmMain.boldButtons(btnName:TButton);
  2.   Function GetButton(const aName:String):TButton;
  3.   var
  4.     vCntr :Integer;
  5.   begin
  6.     Result := nil;
  7.     for vCntr := 0 to ComponentCount -1 do
  8.       if (Components[vCntr] is TButton) and (CompareText(Components[vCntr].Name, aName)=0) then Exit(TButton(Components[vCntr]));
  9.   end;
  10.  
  11. var
  12.   btnNameID   : String; //Holds the String name of the button like: C, D, J
  13.   counterBtn    : Integer; //used for mat
  14.   vBtn             : TButton;
  15. begin
  16.   btnNameID   := Copy(btnName.Name,1,1);
  17.  
  18.   counterBtn:=1;
  19.  
  20.   while counterBtn <> 5 do begin
  21.    {
  22.     Here is what I need to figure out, the btnNameID and the counterBtn will "call" the button name that I already have,
  23.     like B1 or C1 or L5 and them clean up the Font.Style to assing to the right button in the end of the code
  24.     }
  25.     vBtn := GetButton(btnNameID+IntToStr(counterBtn));
  26.     if Assigned(vBtn) then begin
  27.       vBtn.Font.Style=[fsBold];
  28.       vBtn.Caption 'Found you No '+inttostr(CounterBtn);
  29.    end;
  30.     counterBtn:=counterBtn+1;
  31.   end;
  32.   btnName.Font.Style:= [fsBold, fsItalic];
  33. end;    
  34.  

PS: It only finds components placed on the form the method is in. If you have more complicated hierarchies you need to extend it to support sub containers as well.
« Last Edit: November 24, 2017, 05:49:13 am by taazz »
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

douglas.cast

  • Guest
Re: Dynamically change button properties with loops
« Reply #3 on: November 24, 2017, 05:09:57 pm »
Both ways work like a charm, thanks very much.

In the end I used Josh line, considering that I don't have any need to a nil verification (It will always exist at least one button in the form), I'll use a "try" later to fix any process problem.

Code: Pascal  [Select][+][-]
  1.     tbutton(findcomponent(btnNameID+IntToStr(counterBtn))).font.style:=[];
  2.  

But, there is a way to "refine" this search to make it quicker? I'm using a panel to store the buttons, there is a:

Code: [Select]
   tbutton ( findcomponent "inside panel / range " ( btnNameID+IntToStr(counterBtn))).font.style:=[];

Maybe with Pareting or something like it or a with (inside) panel do search

Code: Pascal  [Select][+][-]
  1.    with panel do
  2.       tbutton(findcomponent(btnNameID+IntToStr(counterBtn))).font.style:=[];
  3.    end;
  4.  

I'm just asking because the application have a lot of buttons, 5 searchs on a 100 (or more) buttons list, will be a lot of searching. I also need another awsers before call this procedure, with a lot of buttons I'll probably increase the response time.

Once more, Thanks

RAW

  • Hero Member
  • *****
  • Posts: 868
Re: Dynamically change button properties with loops
« Reply #4 on: November 24, 2017, 06:03:16 pm »
Why don't you use a Button-Array or a Button-List? Isn't that a lot easier if you need to access these buttons regularly?
« Last Edit: November 24, 2017, 07:37:00 pm by RAW »
Windows 7 Pro (x64 Sp1) & Windows XP Pro (x86 Sp3).

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Dynamically change button properties with loops
« Reply #5 on: November 24, 2017, 06:45:50 pm »
Code: Pascal  [Select][+][-]
  1.     tbutton(findcomponent(btnNameID+IntToStr(counterBtn))).font.style:=[];

But, there is a way to "refine" this search to make it quicker?

FindComponent relies on a string comparison of button names, so will be slow for a form with scores of buttons.
Since you know in advance what buttons you have, you can list the ones of font-change interest (as  RAW says) to do a more instantaneous lookup.

As an example: create a new project, generate an OnCreate handler for the main form, and complete the code as follows:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Graphics, StdCtrls, ExtCtrls;
  9.  
  10. type
  11.   TButtonArray = array[1..5] of TButton;
  12.  
  13.   TForm1 = class(TForm)
  14.     procedure FormCreate(Sender: TObject);
  15.   private
  16.     CBtnArray,
  17.     DBtnArray,
  18.     JBtnArray: TButtonArray;
  19.     BtnGroupRG: TRadioGroup;
  20.     EmboldenCB: TCheckBox;
  21.     procedure BtnGroupRGSelectionChange(Sender: TObject);
  22.     procedure CreateButtons;
  23.     procedure CreateRadioGroup;
  24.     procedure CreateCheckBox;
  25.     procedure ButtonGroupEmbolden(aGroup: Integer);
  26.     procedure EmboldenCBChange(Sender: TObject);
  27.   end;
  28.  
  29. var
  30.   Form1: TForm1;
  31.  
  32. implementation
  33.  
  34. {$R *.lfm}
  35.  
  36. procedure TForm1.FormCreate(Sender: TObject);
  37. begin
  38.   SetInitialBounds(0, 0, 670, 210);
  39.   Caption:='Fast button access demo';
  40.   CreateButtons;
  41.   CreateRadioGroup;
  42.   CreateCheckBox;
  43. end;
  44.  
  45. procedure TForm1.CreateButtons;
  46. var
  47.   c: Char;
  48.   i: Integer;
  49.   btn: TButton;
  50.  
  51.   function CreateButton: TButton;
  52.   var
  53.     s: String;
  54.   begin
  55.     Result:=TButton.Create(Self);
  56.     with Result do begin
  57.       Str(i, s);
  58.       Name:=c + s;
  59.       Left:=10 + (Ord(c) - Ord('C')) * 80;
  60.       Top:=10 + Pred(i) * 30;
  61.     end;
  62.   end;
  63.  
  64. begin
  65.   for c:='C' to 'J' do
  66.     for i:=Low(TButtonArray) to High(TButtonArray) do begin
  67.       btn:=CreateButton;
  68.       case c of
  69.         'C': CBtnArray[i]:=btn;
  70.         'D': DBtnArray[i]:=btn;
  71.         'J': JBtnArray[i]:=btn;
  72.       end;
  73.       btn.Parent:=Self;
  74.     end;
  75. end;
  76.  
  77. procedure TForm1.BtnGroupRGSelectionChange(Sender: TObject);
  78. var
  79.   rg: TRadioGroup absolute Sender;
  80. begin
  81.   if Sender is TRadioGroup then
  82.     ButtonGroupEmbolden(rg.ItemIndex);
  83. end;
  84.  
  85. procedure TForm1.CreateRadioGroup;
  86. begin
  87.   BtnGroupRG:=TRadioGroup.Create(Self);
  88.   with BtnGroupRG do begin
  89.     Caption:='Select button group';
  90.     Items.CommaText:='"Group C","Group D","Group J"';
  91.     Columns:=3;
  92.     AutoSize:=True;
  93.     SetInitialBounds(10, 165, 80, 40);
  94.     OnSelectionChanged:=@BtnGroupRGSelectionChange;
  95.     Parent:=Self;
  96.   end;
  97. end;
  98.  
  99. procedure TForm1.CreateCheckBox;
  100. begin
  101.   EmboldenCB:=TCheckBox.Create(Self);
  102.   with EmboldenCB do begin
  103.     Caption:='Embolden button group (set to normal if unchecked)';
  104.     SetInitialBounds(240, 182, 2, 2);
  105.     AutoSize:=True;
  106.     Checked:=True;
  107.     OnChange:=@EmboldenCBChange;
  108.     Parent:=Self;
  109.   end;
  110. end;
  111.  
  112. procedure TForm1.ButtonGroupEmbolden(aGroup: Integer);
  113. var
  114.   ba: TButtonArray;
  115.   b: TButton;
  116. begin
  117.   if not aGroup in [0..2] then
  118.     Exit;
  119.   case aGroup of
  120.     0: ba:=CBtnArray;
  121.     1: ba:=DBtnArray;
  122.     2: ba:=JBtnArray;
  123.   end;
  124.   for b in ba do
  125.     case EmboldenCB.Checked of
  126.       False: b.Font.Style:=[];
  127.       True:  b.Font.Style:=[fsBold];
  128.     end;
  129. end;
  130.  
  131. procedure TForm1.EmboldenCBChange(Sender: TObject);
  132. begin
  133.   if (Sender is TCheckBox) and (BtnGroupRG.ItemIndex > -1) then
  134.       ButtonGroupEmbolden(BtnGroupRG.ItemIndex);
  135. end;
  136.  
  137. end.

douglas.cast

  • Guest
Re: Dynamically change button properties with loops
« Reply #6 on: November 25, 2017, 04:42:51 pm »
I think I express myself in a prety bad way.

The application will create the button dynamically based on a search of a database, let's say I have 5 diferent "languagues" in the database, so I'll have five buttons, one for each panel, the caption will folow the database order, like id 0 = EN_US, id 1 = PT_BR and so on, will be a procedure or function, because there is a field to add more "languages", so I need a way to update the application in real time if necessary.

For this time, I'm trying to fix a weird code that I've done some time ago, so I've created this button in the regular way (by hand inside Lazarus), a array of button or a button list will not make any "visual diference" considering that I'll use procedures and function to control their behavior.

All this buttons will use procedures with their names like a "trigger", one code to all and all of them with diferent names, I'm just tring to change the Font.Style in a "lazy" way, to avoid the creation of the buttons inside of a array / list for now (until I fix the overall  code).

By "groups", I don't realy mean a "mathematical" group, it's just a "expression" to "express" a division of "interactions" that I have, the C1 button belongs to the group "C" by the name, like the P1...P5 belong tho the group "P" and so on.

The overal concept of the ending application:

Code: Pascal  [Select][+][-]
  1.  
  2. Form create
  3. begin
  4.    "var limiter" :=  {read database infomation: number of buttons to be created or read it from a .ini file << Easier way I guess};
  5.    procedure createButtons ("C")
  6.    procedure createButtons ("P")
  7.    procedure createButtons ("V")
  8. end;
  9.  
  10. procedure createButtons (groupName:Char)
  11.  
  12. var
  13.  
  14. //array info
  15.  
  16. btnArray : [1... "limiter"] of Integer;
  17.  
  18. //not sure if this will work btw
  19. groupName : btnArray;
  20.  
  21. i : Integer;
  22.  
  23. btnName : String;
  24.  
  25. begin
  26.  
  27.    //I'll use a panel just called "C", "P", "V" to define the placement of the buttons, thats why I've asked about child / parent
  28.  
  29.  
  30.    while i <> of limiter begin
  31.  
  32.    btnName := groupName+IntToString(i);
  33.  
  34.    create button inside panel "grupName"
  35.  
  36.    button code (
  37.       procedure bold (btnName); //to get the first letter, like "C"
  38.       procedure "another example" (btnName);
  39.    )
  40.  
  41.    button information (
  42.       width, heigth, align, etc
  43.    )
  44.  
  45.    i:=i+1;
  46.  
  47.    end;
  48.  
  49. end;
  50.  
  51. procedure bold (btnName:String);
  52. btnChar : TButton;
  53. btnNum : Integer;
  54. begin
  55.  
  56.    btnChar := Copy (btnName.Name, 1,1);
  57.  
  58.    while i <> of limiter begin
  59.  
  60.       "C"+btnNum.Font.Style := []; //to all of them
  61.  
  62.    end;
  63.  
  64.    btnName.Font.Style:= [fsBold, fsItalic];
  65. end;
  66.  

(Sorry for my child example, my head it's out of place at this moment, this is the best I can do now)

Like C, P or V have a "unlimited" number of elements that can be created, I'm trying to figure out a way to set the "select button from" to a small list, even because I need to set five or maybe seven procedures for each button.

I'm realy sorry for my bad explanation on my first post, I'll try to have a better explanation next time.

jamie

  • Hero Member
  • *****
  • Posts: 6077
Re: Dynamically change button properties with loops
« Reply #7 on: November 25, 2017, 10:04:22 pm »
Maybe you need to look at TActions.

I've never put them to good use but it appears you can make use of them.

With Actions you can assign a list of items that can get handled with a single button
and then later assign that button to some other action etc..
The only true wisdom is knowing you know nothing

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Dynamically change button properties with loops
« Reply #8 on: November 25, 2017, 10:43:27 pm »
I think I express myself in a prety bad way.

You haven't summarised your problem in a concise way. Perhaps because you're still designing your app.
You've certainly made clear that your app creates buttons dynamically.
Is your problem that you're not sure how to keep track of these dynamic components, so you can refer unambiguously to the right one(s) when you want to change their properties?
If so:
you can either design a button container (probably an array or list) which keeps a reference to all the buttons when they are created, and gives you a rapid look-up to retrieve the right button at runtime, although it duplicates the form's own database,
or you can rely on the form's own containers and look the buttons up by Name using the form's Components[] or Controls[] arrays,
or you can use the buttons' Tag property to give each button a unique ID, and look up a button based on its Tag rather than its Name (some people in this forum disapprove of this practice, but I'm not clear why).
If this is not your problem, I suggest you show some compilable code that does not do what you want, and explain what you wish it would do.

douglas.cast

  • Guest
Re: Dynamically change button properties with loops
« Reply #9 on: November 30, 2017, 06:54:18 pm »
Some huge time latter:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Label1: TLabel;
  16.     procedure FormCreate(Sender: TObject);
  17.  
  18.   private
  19.     { private declarations }
  20.   public
  21.     { public declarations }
  22.     { CUSTOM PROCEDURES }
  23.     btnArray: Array of TButton;
  24.  
  25.     procedure btnFontStyles(btn:TButton);
  26.     procedure clickMask(Sender:TObject);
  27.     procedure showMyText(btn:TButton);
  28.   end;
  29.  
  30. var
  31.   Form1  : TForm1;
  32.   M      : TPanel;
  33.  
  34. implementation
  35.  
  36. {$R *.lfm}
  37.  
  38. { TForm1 }
  39.  
  40. procedure TForm1.FormCreate(Sender: TObject);
  41. var
  42.   i, t:Integer;
  43. begin
  44.   t:=0;
  45.   M:= TPanel.Create(Self);
  46.   with M do begin
  47.     BevelInner:=bvNone;
  48.     BevelOuter:=bvNone;
  49.     BevelWidth:=0;
  50.     Width:=Form1.Width;
  51.     Height:=100;
  52.     Color:=clScrollBar;
  53.  
  54.     Parent:=Form1;
  55.   end;
  56.  
  57.   SetLength(btnArray, 5);
  58.  
  59.   for i:=0 to 4 do begin
  60.     t:=t+20;
  61.     btnArray[i]:= TButton.Create(Self);
  62.     with btnArray[i] do begin
  63.       Caption:=IntToStr(i);
  64.       Width:=25;
  65.       Left:=t+20;
  66.       Parent:=M;
  67.       OnClick:=@clickMask;
  68.     end;
  69.   end;
  70. end;
  71.  
  72. procedure TForm1.btnFontStyles(btn: TButton);
  73. var
  74.   c:Integer;
  75. begin
  76.   for c:= 0 to 4 do begin
  77.     with btnArray[c] do begin
  78.       Font.Style:=[];
  79.     end;
  80.   end;
  81.   with btn do begin
  82.     Font.Style:=[fsBold];
  83.     Font.Style:=[fsItalic];
  84.   end;
  85. end;
  86.  
  87. procedure TForm1.clickMask(Sender: TObject);
  88. var
  89.   button:TButton;
  90. begin
  91.   button:=Sender as TButton;
  92.   btnFontStyles(button);
  93.   showMyText(button);
  94. end;
  95.  
  96. procedure TForm1.showMyText(btn:TButton);
  97. begin
  98.   Label1.Caption:='Thank you all, I got it:'+LineEnding+'You have clicked on the button: '+btn.Caption ;
  99. end;
  100.  
  101. end.
  102.  

Any suggestion for improvements are welcome.

Thanks again people that I love S2.
« Last Edit: November 30, 2017, 06:55:53 pm by douglas.cast »

 

TinyPortal © 2005-2018