Recent

Author Topic: Managing a dynamic array of TButton  (Read 6639 times)

Micayah Ritchie

  • New Member
  • *
  • Posts: 18
Managing a dynamic array of TButton
« on: February 23, 2019, 02:42:56 am »
Hello Good Day,
I'm not quite sure if this was a good practice or not, but I had made a habit of using dynamic arrays of TButtons when I want them to do similar things. However, everything I had wanted them to do was always grouped together as one, like so
Code: Pascal  [Select][+][-]
  1.     Setlength(ColorSelectBtn, NumberofColors+1);
  2.     For x:= 1 to NumberofColors do
  3.     begin
  4.       ColorSelectBtn[x]:=TButton.create(nil);
  5.       ColorSelectBtn[x].parent:= MainForm;
  6.       ColorSelectBtn[x].Caption:='Select Color';
  7.       ColorSelectBtn[x].Top:=50;  
  8.    end;  
However now I'm trying to move the button multiple times after it is created so I ended up doing something like this
Code: Pascal  [Select][+][-]
  1. If Someboolean then
  2.   begin
  3.   Setlength(RoundButtons, NumberOfRounds+1);
  4.   For x:= 1 to NumberOfRounds do
  5.   begin
  6.     RoundButtons[x]:=TButton.create(nil);
  7.     RoundButtons[x].parent:= MainForm;
  8.     RoundButtons[x].Caption:='Start Round';
  9.   end;
  10.     SomeBoolean:=false;
  11.   end;
  12.  
  13.   For x:= 1 to NumberOfRounds do
  14.   begin
  15.   RoundButtons[x].Top:=50;
  16.   end;
  17.  

However, this is giving me an error at runtime "External SIGSEGV" which I also get whenever I try to assign a value to an array member that doesn't exist or assign an invaid value to some variable at runtime. Is it implying that the Buttons no longer exist? Does anyone know what I've done wrong? Is there some way I can fix this? If more detail is required about what exactly I'm trying to do, I wouldn't mind sharing.

Also it should be noted that the code still works as follows
Code: Pascal  [Select][+][-]
  1.   Setlength(RoundButtons, NumberOfRounds+1);
  2.   For x:= 1 to NumberOfRounds do
  3.   begin
  4.     RoundButtons[x]:=TButton.create(nil);
  5.     RoundButtons[x].parent:= MainForm;
  6.     RoundButtons[x].Caption:='Start Round';
  7.   end;
  8.  
  9.   For x:= 1 to NumberOfRounds do
  10.   begin
  11.   RoundButtons[x].Top:=50;
  12.   end;
  13.  

However, the boolean there is necessary for me to control when the buttons are created so they aren't constantly recreated when I want to move the buttons

I'm using Lazarus v1.8.4 on a Windows 10. Any help at all would be greatly appreciated

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Managing a dynamic array of TButton
« Reply #1 on: February 23, 2019, 03:31:11 am »
You can not move a button if you did not create it. Try:
Code: Pascal  [Select][+][-]
  1.   If Someboolean then
  2.   begin
  3.     Setlength(RoundButtons, NumberOfRounds+1);
  4.     For x:= 1 to NumberOfRounds do
  5.     begin
  6.       RoundButtons[x]:=TButton.create(nil);
  7.       RoundButtons[x].parent:= MainForm;
  8.       RoundButtons[x].Caption:='Start Round';
  9.     end;
  10.     SomeBoolean:=false;
  11.  
  12.     For x:= 1 to NumberOfRounds do
  13.     begin
  14.       RoundButtons[x].Top:=50;
  15.     end;
  16.   end;

I moved one end to include the second loop in the condition. The code now is equivalent to:
Code: Pascal  [Select][+][-]
  1.   If Someboolean then
  2.   begin
  3.     Setlength(RoundButtons, NumberOfRounds+1);
  4.     For x:= 1 to NumberOfRounds do
  5.     begin
  6.       RoundButtons[x]:=TButton.create(nil);
  7.       RoundButtons[x].parent:= MainForm;
  8.       RoundButtons[x].Caption:='Start Round';
  9.       RoundButtons[x].Top:=50;
  10.     end;
  11.     SomeBoolean:=false;
  12.   end;

Micayah Ritchie

  • New Member
  • *
  • Posts: 18
Re: Managing a dynamic array of TButton
« Reply #2 on: February 23, 2019, 03:53:58 am »
I see what you are doing, but the reason they were separate to begin with is so that there can be other code  in between because it will need to be moved more than once. The purpose of the boolean as I saw it in my head was to make it so that the first block can be called just once and the rest multiple times by just running the same code and whenever the boolean is false it wouldn't reacreate the buttons.
 Also that doesn't solve the issue because then I'll have a new button everytime I want to move it. Is there perhaps some way to destroy the old button when creating a new one?
And finally, I don't think I understand when you mean when you say I did not create the button.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Managing a dynamic array of TButton
« Reply #3 on: February 23, 2019, 04:50:45 am »
I don't think I understand when you mean when you say I did not create the button.
I mean if you did not call:
Code: Pascal  [Select][+][-]
  1.   RoundButtons[x]:=TButton.create(nil);
then you should not call:
Code: Pascal  [Select][+][-]
  1.    RoundButtons[x].Top:=50;

The part of code you posted shows that you move the buttons even if they were not created. That is why I assumed your code is giving SIGSEGV.

To stay on the safe side, use a variable to protect your code against touching buttons if they were not created:
Code: Pascal  [Select][+][-]
  1.   If Someboolean then
  2.   begin
  3.     Setlength(RoundButtons, NumberOfRounds+1);
  4.     For x:= 1 to NumberOfRounds do
  5.     begin
  6.       RoundButtons[x]:=TButton.create(nil);
  7.       RoundButtons[x].parent:= MainForm;
  8.       RoundButtons[x].Caption:='Start Round';
  9.     end;
  10.     SomeBoolean:=false;
  11.     BArrayIsValue := True;
  12.    end;
  13.  
  14.    if BArrayIsValue then
  15.     For x:= 1 to NumberOfRounds do
  16.     begin
  17.       RoundButtons[x].Top:=50;
  18.     end;
« Last Edit: February 23, 2019, 04:56:01 am by engkin »

Handoko

  • Hero Member
  • *****
  • Posts: 5150
  • My goal: build my own game engine using Lazarus
Re: Managing a dynamic array of TButton
« Reply #4 on: February 23, 2019, 05:22:40 am »
Alternatively, I think it will be easier if to use a TPanel and set it as the parent for those buttons. To move the buttons, you only need to move the panel. If you're not happy with the panel border, you can set TPanel.BevelOuter := bvNone;

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Managing a dynamic array of TButton
« Reply #5 on: February 23, 2019, 05:35:09 am »
Talking about alternatives. Instead of using a boolean variable, and when dealing with dynamic arrays use High:
Code: Pascal  [Select][+][-]
  1.   If Someboolean then
  2.   begin
  3.     Setlength(RoundButtons, NumberOfRounds+1);
  4.     For x:= 1 to NumberOfRounds do
  5.     begin
  6.       RoundButtons[x]:=TButton.create(nil);
  7.       RoundButtons[x].parent:= MainForm;
  8.       RoundButtons[x].Caption:='Start Round';
  9.     end;
  10.     SomeBoolean:=false;
  11.    end;
  12.  
  13.     For x:= 1 to High(RoundButtons) do
  14.     begin
  15.       RoundButtons[x].Top:=50;
  16.     end;

But remember to SetLength the array to 0 after you destroy the button.

Micayah Ritchie

  • New Member
  • *
  • Posts: 18
Re: Managing a dynamic array of TButton
« Reply #6 on: February 23, 2019, 05:51:13 am »
I don't think I understand when you mean when you say I did not create the button.
I mean if you did not call:
Code: Pascal  [Select][+][-]
  1.   RoundButtons[x]:=TButton.create(nil);
then you should not call:
Code: Pascal  [Select][+][-]
  1.    RoundButtons[x].Top:=50;

So it has to be called directly after? That can't be the case because if I remove the if statement and there is still code between the for loops then it still works... So now I'm pretty sure the buttons were created. Because if I move the moving of the button into the first loop then it works as well.  Somehow just wrapping it in the if statement is what is causing the error.

Handoko

  • Hero Member
  • *****
  • Posts: 5150
  • My goal: build my own game engine using Lazarus
Re: Managing a dynamic array of TButton
« Reply #7 on: February 23, 2019, 05:53:44 am »
I believe the problem is in:

Code: Pascal  [Select][+][-]
  1. If Someboolean then

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Managing a dynamic array of TButton
« Reply #8 on: February 23, 2019, 06:29:56 am »
So it has to be called directly after?
Nothing in my post implies that it should be called directly.

Again, your problem is accessing the array when it has no buttons. Either use a boolean variable or High as shown in previous posts. Better upload a sample project here. Use PUBLISH from the PROJECT menu. This way the whole logic could be checked.

You could also use the debugger and follow your code to see where it goes wrong.

Micayah Ritchie

  • New Member
  • *
  • Posts: 18
Re: Managing a dynamic array of TButton
« Reply #9 on: February 23, 2019, 06:56:02 am »
Thank you for your suggestion, I've tried using the debugger but I don't really understand how it works that well, I'll find some tutorials. How does publishing the project work? Also I still don't understand why the if statement would cause the buttons to no longer exist.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Managing a dynamic array of TButton
« Reply #10 on: February 23, 2019, 07:18:34 am »
I've tried using the debugger but I don't really understand how it works that well, I'll find some tutorials.
Checking tutorials is crucial. To get you started:

Use F5 to create a "break point" where you want the debugger to stop the application at.

Use F8 to move to the next statement.

Use F7 to step inside a call to a function or procedure.

Use the mouse to point at variables to see their values at that point.

How does publishing the project work?
Try it. It creates a folder, by default named: publishedproject, inside your project folder with a copy of the source code of your project.  Zip it and upload it here.

Also I still don't understand why the if statement would cause the buttons to no longer exist.
Maybe because the condition is FALSE, so no buttons are created?

Micayah Ritchie

  • New Member
  • *
  • Posts: 18
Re: Managing a dynamic array of TButton
« Reply #11 on: February 23, 2019, 07:40:53 am »
I'm pretty sure that the condition is true. It was declared true outside the procedure in which the code I posted was, and the buttons do appear without error if I don't move it. Interestingly though, if I make it true inside the procedure then it does work when I move the buttons, however this does the same thing as the original problem.

Also I'll try to upload the project, but It's poorly annotated and I'm not sure you'll get the point of what I'm trying to do but I will.

Edit: Also the project acesses files stored on the device in the project's folder so should I just zip the project folder?
« Last Edit: February 23, 2019, 07:45:47 am by Micayah Ritchie »

Thaddy

  • Hero Member
  • *****
  • Posts: 14364
  • Sensorship about opinions does not belong here.
Re: Managing a dynamic array of TButton
« Reply #12 on: February 23, 2019, 10:25:59 am »
Set the parent with the constructor. (form or Panel) Don't create with nil. E.g. some messages go to the parent window before they go to the buttons, so if the parent is not set you will miss those.
Also highly likely to cause memory leaks if you forget to manually free them. If the parent /owner is known that will take care of cleaning up the controls when it is destroyed.
I see you set the parent, but this is not really  the owner. If you do TButton.Create(MainForm);  that will be done for you.

Note the difference:
"The Parent property declared in TControl is similar to the Owner property declared in TComponent, in that the Parent of a control frees the control just as the Owner of a component frees that Component. However, the Parent of a control is always a windowed control that visually contains the control, and is responsible for writing the control to a stream when the form is saved. The Owner of a component is the component that was passed as a parameter in the constructor and, if assigned, initiates the process of saving all objects (including the control and its parent) when the form is saved."

This is from the Delphi docs, but the same holds true for Freepascal and Lazarus. And the constructor does more afaik, so try this first:
Code: Pascal  [Select][+][-]
  1.      RoundButtons[x]:=TButton.create(mainform);
  2.       // and leave this out RoundButtons[x].parent:= MainForm;

And more importantly (and a possible cause of a SIGSEV): dynamic arrays count from zero and you set from one so you are running the risk of over-indexing the array by one........ (Although you use high() zero is empty. Should be:
Code: Pascal  [Select][+][-]
  1.     Setlength(RoundButtons, NumberOfRounds);
  2.     For x:= 0 to High(NumberOfRounds) do
« Last Edit: February 23, 2019, 11:25:20 am by Thaddy »
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

Micayah Ritchie

  • New Member
  • *
  • Posts: 18
Re: Managing a dynamic array of TButton
« Reply #13 on: February 23, 2019, 04:05:25 pm »
The array I used was set to +1 because I know the array starts at zero, it's just easier for me to follow the logic if shift it up but thank you.

Thaddy

  • Hero Member
  • *****
  • Posts: 14364
  • Sensorship about opinions does not belong here.
Re: Managing a dynamic array of TButton
« Reply #14 on: February 23, 2019, 04:07:48 pm »
I can't follow that logic: Array[0] will be nil in that case...
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

 

TinyPortal © 2005-2018