Recent

Author Topic: Help with component creation [Resolved]  (Read 2460 times)

cursosba

  • New Member
  • *
  • Posts: 18
Help with component creation [Resolved]
« on: August 20, 2024, 01:50:59 pm »
Hello everyone, I'm new to creating components, I created the following code, it creates a custom panel and inside it a button, in design time one button appears as it should be, but in run time two appear, below is the code and images.

Code: Pascal  [Select][+][-]
  1. unit MyCustomPanel;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, Controls, ExtCtrls, StdCtrls, SysUtils;
  7.  
  8. type
  9.   TCustomPanelWithButton = class(TPanel)
  10.   private
  11.     FButton: TButton;
  12.     procedure SetButtonCaption(const Value: string);
  13.     function GetButtonCaption: string;
  14.   protected
  15.     procedure CreateWnd; override;
  16.     procedure SetButtonPosition;
  17.     procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  18.   public
  19.     constructor Create(AOwner: TComponent); override;
  20.     destructor Destroy; override;
  21.     property Button: TButton read FButton;
  22.   published
  23.     property ButtonCaption: string read GetButtonCaption write SetButtonCaption;
  24.   end;
  25.  
  26. procedure Register;
  27.  
  28. implementation
  29.  
  30. procedure Register;
  31. begin
  32.   RegisterComponents('Samples', [TCustomPanelWithButton]);
  33. end;
  34.  
  35. { TCustomPanelWithButton }
  36.  
  37. constructor TCustomPanelWithButton.Create(AOwner: TComponent);
  38. begin
  39.   inherited Create(AOwner);
  40.  
  41.   FButton := TButton.Create(Self);
  42.   FButton.Parent := Self;
  43.   FButton.Left := 10;
  44.   FButton.Top := 10;
  45.   FButton.Width := 75;
  46.   FButton.Height := 25;
  47.  
  48.   FButton.SetSubComponent(True);
  49.   FButton.Name := 'MyButton';
  50.   FButton.Caption := 'Button';
  51.  
  52.   SetButtonPosition;
  53. end;
  54.  
  55. destructor TCustomPanelWithButton.Destroy;
  56. begin
  57.   FButton.Free;
  58.   inherited Destroy;
  59. end;
  60.  
  61. procedure TCustomPanelWithButton.CreateWnd;
  62. begin
  63.   inherited CreateWnd;
  64.   SetButtonPosition;
  65. end;
  66.  
  67. procedure TCustomPanelWithButton.SetButtonPosition;
  68. begin
  69.   FButton.Left := (Width - FButton.Width) div 2;
  70.   FButton.Top := (Height - FButton.Height) div 2;
  71. end;
  72.  
  73. procedure TCustomPanelWithButton.SetButtonCaption(const Value: string);
  74. begin
  75.   FButton.Caption := Value;
  76. end;
  77.  
  78. function TCustomPanelWithButton.GetButtonCaption: string;
  79. begin
  80.   Result := FButton.Caption;
  81. end;
  82.  
  83. procedure TCustomPanelWithButton.GetChildren(Proc: TGetChildProc; Root: TComponent);
  84. begin
  85.   if Assigned(FButton) then
  86.     Proc(FButton);
  87. end;
  88.  
  89. initialization
  90.   RegisterClass(TButton);
  91.  
  92. end.
  93.  
  94.  
« Last Edit: August 22, 2024, 07:28:24 pm by cursosba »

bobby100

  • Sr. Member
  • ****
  • Posts: 260
    • Malzilla
Re: Help with component creation
« Reply #1 on: August 20, 2024, 04:44:11 pm »
Isn't MyButton the 2nd button?

wp

  • Hero Member
  • *****
  • Posts: 12401
Re: Help with component creation
« Reply #2 on: August 20, 2024, 05:06:51 pm »
Being on Manjaro Linux / qt6 I can confirm the issue after pasting your code into a separate unit and installing it via an own package. I cannot confirm when the component is created at runtime where there is only a single button.

I think (not 100% sure, though) the issue is in your intention to have the button in the object tree of the object inspector. For this purpose you override the method GetChildren. When I comment this method out and rebuild the IDE, the button appears only once - but now there is no "MyButton" subnode in the object tree underneath "CustomPanelWithButton", and you don't have access to the button any more.

Another way to stream the button properties would be to move its declaration from the public to the published section of the class. Now - after rebuilding the IDE again - the button can be found as a regular property of the control, and you can control its properties.

But do you really want this? Your code centers the button inside the panel. But when the user has access to the button properties it can overwrite the Left and Top properties and move it somewhere else at designtime - but at runtime it will be in the center again due to "SetButtonPosition" in "CreateWnd". A very confusing behaviour.

I did not test this, but I am sure that the button in the object tree will lead to the same issue.

I'd rather provide separate properties for those button properties you would allow the user to change. Like you do it with "ButtonCaption".

Some few other remarks about what stroke my eyes:
  • I'd remove FButton.SetSubComponent from Create because as a TCustomPanel descendant the component already knows that all child controls are subcomponents.
  • Remove the overridden destructor. FButton is created with AOwner = self (=the component), and therefore, the component automatically takes care of destroying the button.
  • You must also add an overridden DoOnResize method calling SetButtonPosition after inherited so that the button remains centered after resizing the control.
  • Why is there a call to RegisterClass in the initialization section? Normally this is handled by the LCL itself.
  • Using a panel as ancestor for your component has the risk that the user adds other controls which may interfere with the button. Is this intended? If not you could override ValidateInsert and inhibit insertion of any controls once FButton is in the Controls list of the panel.
« Last Edit: August 20, 2024, 05:19:27 pm by wp »

Handoko

  • Hero Member
  • *****
  • Posts: 5336
  • My goal: build my own game engine using Lazarus
Re: Help with component creation
« Reply #3 on: August 20, 2024, 06:32:01 pm »
Done.

I fixed OP code, it does not show double buttons now. Tested on Lazarus 3.4 Linux GTK2.

@cursosba
If you have bug in your code, please provide the whole compile-able code. So we can test your exact code and we no need to do the copy/paste thing.

wp

  • Hero Member
  • *****
  • Posts: 12401
Re: Help with component creation
« Reply #4 on: August 20, 2024, 10:05:46 pm »
@Handoko: If I understand your code correctly, you are only removing the "SetButtonPosition" from the "CreateWnd" method. It's hard to believe how this could avoid double creation of the button. And in fact, when you add "Caption := IntToStr(MyCustomPanel1.ControlCount)" to the "OnShow" handler of the form you'll see that there are still two controls inside the panel. They are overlapping which you can see by separating them when you move the Button to somewhere else.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormShow(Sender:TObject);
  2. begin
  3.   Caption := IntToStr(MyCustomPanel1.ControlCount);
  4.   MyCustomPanel1.Button.Left :=0;
  5.   MyCustomPanel1.Button.Top := 0;
  6.   MyCustomPanel1.Button.Caption := '123';
  7. end;

I think the button is created twice during design-time. Once because it is explicitly created along with the panel when its contructor is called. And once this button is on the panel it is streamed like any other control added to a panel.

Some debugging shows that when FButton is created in the constructor of the panel  the lfm file has not yet been read. This gives the idea for another solution: Override the "Loaded" procedure (which is called when lfm-loading is finished) and delete the second button:

Code: Pascal  [Select][+][-]
  1. procedure TMyCustomPanel.Loaded;
  2. begin
  3.   inherited;
  4.   if (ControlCount >= 2) and (Controls[1] is TButton) then
  5.     Controls[1].Free;
  6. end;

Maybe more checks are needed to avoid issues when other controls have been added to the panel.


Handoko

  • Hero Member
  • *****
  • Posts: 5336
  • My goal: build my own game engine using Lazarus
Re: Help with component creation
« Reply #5 on: August 20, 2024, 10:21:53 pm »
No.

I just simple moved that property to published section. I wasn't sure how it can fix it, I write many classes but not the drag-and-drop components, I rarely use published section.

After several tests, I was sure OP's problem has been fixed. After I posted my solution, I saw you mentioned moving the property to published section may fix it too. Perhaps you can provide the explanation.

wp

  • Hero Member
  • *****
  • Posts: 12401
Re: Help with component creation
« Reply #6 on: August 21, 2024, 10:05:13 am »
I repeated the tests that I made for reply #2:

The issue goes away when the call to GetChildren() is removed. A "Caption := IntToStr(CustomPanelWithButton)" in FormShow displays 1, i.e. there is only one button in the control.
In order to get access to the button properties in the Object Inspector (because the button now is no longer displayed in the object tree), I moved the Button declaration from public to published.

In handoko's code the Button property only has been published keeping the GetChildren call. Therefore the issue is not gone.

Looking at the lfm file it can be seen that GetChildren kind of publishes the Button properties since they are listed in the lfm, and the button will be created by the streaming process. Together with its manual creation n the control's constructor there are now two buttons in the panel.

cursosba

  • New Member
  • *
  • Posts: 18
Re: Help with component creation
« Reply #7 on: August 22, 2024, 07:30:50 pm »
@Handoko: If I understand your code correctly, you are only removing the "SetButtonPosition" from the "CreateWnd" method. It's hard to believe how this could avoid double creation of the button. And in fact, when you add "Caption := IntToStr(MyCustomPanel1.ControlCount)" to the "OnShow" handler of the form you'll see that there are still two controls inside the panel. They are overlapping which you can see by separating them when you move the Button to somewhere else.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormShow(Sender:TObject);
  2. begin
  3.   Caption := IntToStr(MyCustomPanel1.ControlCount);
  4.   MyCustomPanel1.Button.Left :=0;
  5.   MyCustomPanel1.Button.Top := 0;
  6.   MyCustomPanel1.Button.Caption := '123';
  7. end;

I think the button is created twice during design-time. Once because it is explicitly created along with the panel when its contructor is called. And once this button is on the panel it is streamed like any other control added to a panel.

Some debugging shows that when FButton is created in the constructor of the panel  the lfm file has not yet been read. This gives the idea for another solution: Override the "Loaded" procedure (which is called when lfm-loading is finished) and delete the second button:

Code: Pascal  [Select][+][-]
  1. procedure TMyCustomPanel.Loaded;
  2. begin
  3.   inherited;
  4.   if (ControlCount >= 2) and (Controls[1] is TButton) then
  5.     Controls[1].Free;
  6. end;

Maybe more checks are needed to avoid issues when other controls have been added to the panel.

wp, with your tip I managed to solve it, now in run time only one button appears. thanks!

Code: Pascal  [Select][+][-]
  1. [code=pascal]unit MyCustomPanel;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, Controls, ExtCtrls, StdCtrls, SysUtils;
  7.  
  8. type
  9.  
  10.   { TCustomPanelWithButton }
  11.  
  12.   TCustomPanelWithButton = class(TPanel)
  13.   private
  14.     FButton: TButton;
  15.     procedure SetButtonCaption(const Value: string);
  16.     function GetButtonCaption: string;
  17.   protected
  18.     procedure CreateWnd; override;
  19.     procedure SetButtonPosition;
  20.     procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  21.   public
  22.     constructor Create(AOwner: TComponent); override;
  23.     destructor Destroy; override;
  24.     procedure Loaded; override;
  25.   published
  26.     property Button: TButton read FButton;
  27.     property ButtonCaption: string read GetButtonCaption write SetButtonCaption;
  28.   end;
  29.  
  30. procedure Register;
  31.  
  32. implementation
  33.  
  34. procedure Register;
  35. begin
  36.   RegisterComponents('Samples', [TCustomPanelWithButton]);
  37. end;
  38.  
  39. { TCustomPanelWithButton }
  40.  
  41. constructor TCustomPanelWithButton.Create(AOwner: TComponent);
  42. begin
  43.   inherited Create(AOwner);
  44.  
  45.   FButton := TButton.Create(Self);
  46.   FButton.Parent := Self;
  47.   FButton.Left := 10;
  48.   FButton.Top := 10;
  49.   FButton.Width := 75;
  50.   FButton.Height := 25;
  51.  
  52.   FButton.SetSubComponent(True);
  53.   FButton.Name := 'MyButton';
  54.   FButton.Caption := 'Button';
  55.  
  56.   SetButtonPosition;
  57. end;
  58.  
  59. destructor TCustomPanelWithButton.Destroy;
  60. begin
  61.   FButton.Free;
  62.   inherited Destroy;
  63. end;
  64.  
  65. procedure TCustomPanelWithButton.Loaded;
  66. begin
  67.   inherited;
  68.   if (ControlCount >= 2) and (Controls[1] is TButton) then
  69.     Controls[1].Free;
  70. end;
  71.  
  72. procedure TCustomPanelWithButton.CreateWnd;
  73. begin
  74.   inherited CreateWnd;
  75. end;
  76.  
  77. procedure TCustomPanelWithButton.SetButtonPosition;
  78. begin
  79.   FButton.Left := (Width - FButton.Width) div 2;
  80.   FButton.Top := (Height - FButton.Height) div 2;
  81. end;
  82.  
  83. procedure TCustomPanelWithButton.SetButtonCaption(const Value: string);
  84. begin
  85.   FButton.Caption := Value;
  86. end;
  87.  
  88. function TCustomPanelWithButton.GetButtonCaption: string;
  89. begin
  90.   Result := FButton.Caption;
  91. end;
  92.  
  93. procedure TCustomPanelWithButton.GetChildren(Proc: TGetChildProc; Root: TComponent);
  94. begin
  95.   if Assigned(FButton) then
  96.     Proc(FButton);
  97. end;
  98.  
  99. initialization
  100.   RegisterClass(TButton);
  101.  
  102. end.
  103.  
[/code]


cursosba

  • New Member
  • *
  • Posts: 18
Re: Help with component creation [Resolved]
« Reply #8 on: August 22, 2024, 07:33:31 pm »
bobby100, Handoko, wp, thank you for your help, now I can develop my studies.  :D :D

 

TinyPortal © 2005-2018