Recent

Author Topic: trying to create an own component but getting SIGSEGV  (Read 6296 times)

r.lukasiak

  • Full Member
  • ***
  • Posts: 138
trying to create an own component but getting SIGSEGV
« on: August 17, 2022, 06:12:41 pm »
Hi everyone!

I'm trying to figure out how to create my own component, I don't really need to register it, it's to be used on runtime.
What I need is a Tpanel (or TImage) that holds some other components like TLabel, TButton etc. I want it to be created all together when I call myvar:=Tmycom.Create(...);

what I got:
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.   Ttest = class(TCustomControl)
  13.     private
  14.       bkg: TPanel;
  15.       title: TLabel;
  16.       btn: TButton;
  17.     public
  18.       constructor Create(AOwner: TComponent);
  19.   end;
  20.  
  21.  
  22.   { TForm1 }
  23.  
  24.   TForm1 = class(TForm)
  25.     Panel1: TPanel;
  26.     procedure Panel1Click(Sender: TObject);
  27.   private
  28.  
  29.   public
  30.  
  31.   end;
  32.  
  33. var
  34.   Form1: TForm1;
  35.   test: Ttest;
  36.  
  37. implementation
  38.  
  39.   { TForm1 }
  40.  
  41.   procedure TForm1.Panel1Click(Sender: TObject);
  42.   begin
  43.     test:=Ttest.Create(Form1);
  44.     test.Parent:=Panel1;
  45.   end;
  46.  
  47.   constructor Ttest.Create(AOwner: TComponent);
  48.   begin
  49.     bkg:=Tpanel.Create(aOwner);
  50.     bkg.Parent:=Self;
  51.     bkg.Align:=alTop; //If I comment this line,
  52.                       //I don't get Project project1 raised exception class 'External: SIGSEGV'
  53.     title:=TLabel.Create(AOwner);
  54.     title.Parent:=bkg;
  55.     title.Align:=alTop;}
  56.     btn:=Tbutton.Create(AOwner);
  57.     btn.Align:=alBottom;}
  58.   end;
  59.  
  60. {$R *.lfm}
  61.  
  62. end.
  63.  

Without the line I commented, I get nothing. When I uncomment it, I get Project project1 raised exception class 'External: SIGSEGV'

I was googling for a while, I found a couple of similar topics, I'm improvising basing on those posts but I can't figure it out.

any hint?
thanks in advance!

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9792
  • Debugger - SynEdit - and more
    • wiki
Re: trying to create an own component but getting SIGSEGV
« Reply #1 on: August 17, 2022, 06:29:59 pm »
Try
Code: Pascal  [Select][+][-]
  1.       inherited Create(AOwner);

At the top of your constructor.

And also set the position of your component inside the panel (e.g.: "test.Align := alClient;")

r.lukasiak

  • Full Member
  • ***
  • Posts: 138
Re: trying to create an own component but getting SIGSEGV
« Reply #2 on: August 17, 2022, 06:31:28 pm »
I think I've just found a solution.
I changed the class to:
Code: Pascal  [Select][+][-]
  1. Ttest = class(TPanel)
  2.     private
  3.       title: TLabel;
  4.       btn: TButton;
  5.     public
  6.       constructor Create(AOwner: TComponent);
  7.   end;    
  8.  
  9.  
  10.   constructor Ttest.Create(AOwner: TComponent);
  11.   begin
  12.     inherited;
  13.     Self.Caption:='My Panel';
  14.     title:=TLabel.Create(AOwner);
  15.     title.Parent:=Self;
  16.     title.Align:=alTop;
  17.     title.Caption:='My Label';
  18.     btn:=TButton.Create(AOwner);
  19.     btn.Parent:=Self;
  20.     btn.Align:=alBottom;
  21.     btn.Caption:='My Button';
  22.   end;    
  23.  

and it works as expected but now....
how can I link Button's OnClick event to my own OnMyButtonClick? I need my own OnClick event that "reacts" to Button's OnClick.

greetings
« Last Edit: August 17, 2022, 06:38:56 pm by r.lukasiak »

r.lukasiak

  • Full Member
  • ***
  • Posts: 138
Re: trying to create an own component but getting SIGSEGV
« Reply #3 on: August 17, 2022, 06:34:52 pm »
Thanks for your asnwer @Martin_fr, I was typing my post when you answered, it looks like I was missing the inherited clause.
Would my solution be any good?

greetings


Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: trying to create an own component but getting SIGSEGV
« Reply #5 on: August 17, 2022, 07:12:51 pm »
how can I link Button's OnClick event to my own OnMyButtonClick? I need my own OnClick event that "reacts" to Button's OnClick.

I modified your code:

Code: Pascal  [Select][+][-]
  1. Type
  2.   TTest = class(TPanel)
  3.     private
  4.       Title: TLabel;
  5.       Btn:   TButton;
  6.       procedure MyButtonClick(Sender: TObject);
  7.     public
  8.       constructor Create(AOwner: TComponent);
  9.     end;
  10.  
  11. implementation
  12.  
  13. procedure TTest.MyButtonClick(Sender: TObject);
  14. begin
  15.   Btn.Caption := 'Clicked!';
  16. end;
  17.  
  18. constructor TTest.Create(AOwner: TComponent);
  19. begin
  20.   if not(AOwner is TWinControl) then Exit; // Invalid owner
  21.   inherited;
  22.   Parent        := (AOwner as TWinControl);
  23.   Caption       := '';
  24.   Title         := TLabel.Create(AOwner);
  25.   Title.Parent  := Self;
  26.   Title.Align   := alTop;
  27.   Title.Caption := 'My Label';
  28.   Btn           := TButton.Create(AOwner);
  29.   Btn.Parent    := Self;
  30.   Btn.Align     := alBottom;
  31.   Btn.Caption   := 'Click me';
  32.   Btn.OnClick   := @MyButtonClick;
  33. end;

To use it on your mainform:
    myTest := TTest.Create(Self);

~~~edit~~~
Warning: the code above has an issue. To fix it, you can use rvk's suggestion (see the post below) or write a destructor to free the items.
« Last Edit: August 17, 2022, 07:24:36 pm by Handoko »

rvk

  • Hero Member
  • *****
  • Posts: 6111
Re: trying to create an own component but getting SIGSEGV
« Reply #6 on: August 17, 2022, 07:19:37 pm »
I would use TLabel.Create(Self) instead of TLabel.Create(AOwner) in your component (also for the TButton).

You would need to create a destroy event cleaning up the TLabel and TButton but that's better than to let the caller do this.

Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: trying to create an own component but getting SIGSEGV
« Reply #7 on: August 17, 2022, 07:21:02 pm »
You're right. I didn't pay attention on that. Good suggestion.

HeavyUser

  • Sr. Member
  • ****
  • Posts: 397
Re: trying to create an own component but getting SIGSEGV
« Reply #8 on: August 17, 2022, 07:49:28 pm »
You would need to create a destroy event cleaning up the TLabel and TButton but that's better than to let the caller do this.
He only needs to explicitly destroy the components if the owner is nil, otherwise the owner will auto destroy them during its destruction.

rvk

  • Hero Member
  • *****
  • Posts: 6111
Re: trying to create an own component but getting SIGSEGV
« Reply #9 on: August 17, 2022, 07:54:19 pm »
You would need to create a destroy event cleaning up the TLabel and TButton but that's better than to let the caller do this.
He only needs to explicitly destroy the components if the owner is nil, otherwise the owner will auto destroy them during its destruction.
Ah, yes. Missed that.
Because Self is a TPanel itself, it's a TComponent which cleans up after itself (including all it owns).

r.lukasiak

  • Full Member
  • ***
  • Posts: 138
Re: trying to create an own component but getting SIGSEGV
« Reply #10 on: August 17, 2022, 10:17:16 pm »
@Handoko thanks for the links, great article but I think it's a little bit overwhelming for me, at least for now :D
I was hoping for solution like Btn.OnClick:=@MyButtonClick; and from your code I see it's doable this way. Thanks a lot!
I will take care of the "freeing" issue.
Thanks everyone, it works as I needed.

And what would change if I used TImage instead of TPanel? TImage can't be "a parent", right? I'm trying to use TImage but I can't seem to have my components visible (since I can't assign a parent).
I need something that I can draw with BGRABitmap to have a transparent background. I used TBCPanel on Linux (QT5) but it's not transparent on Windows (for some reason) and I can't figure out how to draw on TPanel with BGRABitmap.




« Last Edit: August 17, 2022, 10:23:02 pm by r.lukasiak »

HeavyUser

  • Sr. Member
  • ****
  • Posts: 397
Re: trying to create an own component but getting SIGSEGV
« Reply #11 on: August 18, 2022, 12:41:10 am »
@Handoko thanks for the links, great article but I think it's a little bit overwhelming for me, at least for now :D
I was hoping for solution like Btn.OnClick:=@MyButtonClick; and from your code I see it's doable this way. Thanks a lot!
I will take care of the "freeing" issue.
Thanks everyone, it works as I needed.

And what would change if I used TImage instead of TPanel? TImage can't be "a parent", right? I'm trying to use TImage but I can't seem to have my components visible (since I can't assign a parent).
I need something that I can draw with BGRABitmap to have a transparent background. I used TBCPanel on Linux (QT5) but it's not transparent on Windows (for some reason) and I can't figure out how to draw on TPanel with BGRABitmap.
Code: Pascal  [Select][+][-]
  1.  
  2.   { Ttest }
  3.  
  4.   Ttest = class(TPanel)
  5.   private
  6.     title: TLabel;
  7.     btn: TButton;
  8.     function GetButtonClick: TNotifyEvent;
  9.     procedure SetButtonClick(AValue: TNotifyEvent);
  10.   public
  11.     constructor Create(AOwner: TComponent);override;
  12.  
  13.     property ButtonClick : TNotifyEvent read GetButtonClick write SetButtonClick;
  14.   end;
  15.  
  16. function Ttest.GetButtonClick: TNotifyEvent;
  17. begin
  18.   Result := btn.OnClick;
  19. end;
  20.  
  21. procedure Ttest.SetButtonClick(AValue: TNotifyEvent);
  22. begin
  23.   btn.OnClick := AValue;
  24. end;
  25.  
  26. constructor Ttest.Create(AOwner: TComponent);
  27. begin
  28.   inherited;
  29.   Self.Caption:='My Panel';
  30.   title:=TLabel.Create(AOwner);
  31.   title.Parent:=Self;
  32.   title.Align:=alTop;
  33.   title.Caption:='My Label';
  34.   btn:=TButton.Create(AOwner);
  35.   btn.Parent:=Self;
  36.   btn.Align:=alBottom;
  37.   btn.Caption:='My Button';
  38. end;
  39.  
  40.  
here you go you simply assign an onclick even to the ButtonClick property and the button will call it.

PS.
Everything was typed directly on the browser no testing or compilation took place in this example you might have to overcome an error or two.
« Last Edit: August 18, 2022, 01:13:10 am by HeavyUser »

r.lukasiak

  • Full Member
  • ***
  • Posts: 138
Re: trying to create an own component but getting SIGSEGV
« Reply #12 on: August 18, 2022, 03:17:57 am »
@HeavyUser thanks for your answer, this is already solved but does you solution have some advantages over Handoko's solution (Btn.OnClick:=@MyButtonClick)?

The Panel with custom drawn transparent background is kinda solved. Since TPanel is transparent on both Windows and Linux, I just put TImage on top of it and custom draw it. However, for learning purposes and better understanding... is it possible to replace TPanel with TImage?

Now I faced another obstacle...

now my class is:
Code: Pascal  [Select][+][-]
  1. Ttest = class(TPanel)
  2.     private
  3.       bkg: TImage;
  4.       title: TLabel;
  5.       btn: TButton;
  6.     public
  7.       MyLabel: string; //this value is supposed to be displayed in Title.Caption, it may be changed on runtime, any time
  8.       constructor Create(AOwner: TComponent);
  9.       procedure ButtonClick(Sender: TObject);
  10.       procedure BkgPaint(Sender: TObject);
  11.   end;

How can I make TLabel.Caption=MyLabel? I mean when I update the value MyTest.MyLabel:='some value', how the TTest may know the value changed and need to update Title (TLabel) ?


----------------------------------------------
UPDATE:

I somehow solved it by adding TTest.SetTitle procedure but is there any way to have the same effect by just MyTest.MyLabel:='...'?
« Last Edit: August 18, 2022, 03:45:18 am by r.lukasiak »

HeavyUser

  • Sr. Member
  • ****
  • Posts: 397
Re: trying to create an own component but getting SIGSEGV
« Reply #13 on: August 18, 2022, 09:28:25 am »
@HeavyUser thanks for your answer, this is already solved but does you solution have some advantages over Handoko's solution (Btn.OnClick:=@MyButtonClick)?
I made the event public so it can be assigned from outside the control it self that's it.

Handoko

  • Hero Member
  • *****
  • Posts: 5131
  • My goal: build my own game engine using Lazarus
Re: trying to create an own component but getting SIGSEGV
« Reply #14 on: August 18, 2022, 02:07:38 pm »
HeavyUser's code used getter and setter, that was needed to create a property (see his code line #13), and he made the property public. Mine cannot be accessed from outside because the code of MyButtonClick (my version) was provided by the class itself.

About object ownership. If an object has an owner, the owner is responsible for freeing the object. That's why they said we don't need to free it manually. It is my habit, to free all the memory and objects I requested/created, including the ones have owner. You don't have to, it is up to you.

However, for learning purposes and better understanding... is it possible to replace TPanel with TImage?

Yes, you can. It depend on what you want to achieve, each component has its own advantages/disadvantages. For example: TPanel supports tabstop but no picture. TImage supports picture but no tabstop. Although TPanel does not support picture by default, sure you can write code for making it to show picture.

There are many different ways to write a component. You can combine some components to became a new one, you can inherit from a component and add some new features, and you can write it from scratch.

I wrote a demo showing how to write a simple component by inherited from TCustomControl. I prefer TCustomControl because it is lightweight and it already provides many useful features like caption, border, setbound, onclick, etc.

Code: Pascal  [Select][+][-]
  1. unit MyComponent;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Controls, ExtCtrls, DateUtils;
  9.  
  10. type
  11.  
  12.   { TMyComponent }
  13.  
  14.   TMyComponent = class(TCustomControl)
  15.   strict private
  16.     FTimer: TTimer;
  17.     procedure OnTimer(Sender: TObject);
  18.   public
  19.     procedure   Paint; override;
  20.     constructor Create(AOwner: TComponent); override;
  21.     destructor  Destroy; override;
  22.     procedure   ShowTime;
  23.   end;
  24.  
  25. implementation
  26.  
  27. { TMyComponent }
  28.  
  29. procedure TMyComponent.OnTimer(Sender: TObject);
  30. var
  31.   Current: TDateTime;
  32. begin
  33.   Current := Time;
  34.   Caption := HourOf(Current).ToString + ':' + MinuteOf(Current).ToString + ':' +
  35.              SecondOf(Current).ToString;
  36.   Invalidate;
  37. end;
  38.  
  39. procedure TMyComponent.Paint;
  40. var
  41.   PosX: Integer;
  42.   PosY: Integer;
  43. begin
  44.   inherited;
  45.   PosX := (Width  - Canvas.GetTextWidth(Caption))  div 2; // Calculate center x
  46.   PosY := (Height - Canvas.GetTextHeight(Caption)) div 2; // Calculate center y
  47.   Canvas.Brush.Color := Color;
  48.   Canvas.TextOut(PosX, PosY, Caption);
  49. end;
  50.  
  51. constructor TMyComponent.Create(AOwner: TComponent);
  52. begin
  53.   inherited;
  54.  
  55.   // Set default values
  56.   FTimer      := nil;
  57.   Caption     := 'New';
  58.   Width       := 80;
  59.   Height      := 30;
  60.   BorderStyle := bsSingle;
  61.   if AOwner is TWinControl then
  62.     Parent  := (AOwner as TWinControl);
  63.  
  64. end;
  65.  
  66. destructor TMyComponent.Destroy;
  67. begin
  68.   if Assigned(FTimer) then
  69.     FTimer.Free;
  70. end;
  71.  
  72. procedure TMyComponent.ShowTime;
  73. begin
  74.   FTimer := TTimer.Create(nil);
  75.   FTimer.Interval := 1000;
  76.   FTimer.OnTimer  := @OnTimer;
  77. end;
  78.  
  79. end.

Line #16

Programmers usually add an F at the beginning of the name of a class's field.

Line #15..#17

FTimer and OnTimer should be in private section. I personally prefer strict private.

Line #19..#22

Each of them needs an override modifier. Check the documentation if you want to learn more.

Line #29
OnTimer will be called by FTimer every second.

Line #36
Call invalidate to let the system know that the data has been changed and need to redraw its appearance.

Line #39

Calculate and show the caption at the center of the component.

Line #61

Only TWinControl (and its inheritance) can be a parent.

Line #69

If it is created, it should be freed. (but you don't have to free it manually if it has an owner)

Line #72

Create a timer for showing time.

 

TinyPortal © 2005-2018