Recent

Author Topic: Custom self drawn component issue  (Read 2052 times)

Artur W.

  • New member
  • *
  • Posts: 9
Custom self drawn component issue
« on: February 05, 2025, 07:15:40 pm »
Hello!

I'm coming to FPC/Lazarus from the C++/Python world - last time I had to do with Pascal was under DOS/Win3.11 and Win95, using TV2/BP7. As you imagine, it was quite some time ago...
Anyway, I've decided to de-dust my Pascal skills or rather re-learn, and am porting some of the small codebase from C++. It goes quite well but now I came to an issue.

I am prototyping a small interface that drawn completely on TCanvas. To that end, I have a component (TCustomComponent descendant) that's created in 10 copies, displaying 10 different sets of data.
It shows but fails with drawing a frame and a background - if, however, I put a TPicture there and draw it, the picture itself is drawn properly. I understand that I am missing some logic but am also out of ideas as of what am I missing. My approach is based on this bit of documentation: https://wiki.freepascal.org/Developing_with_Graphics#Create_a_custom_control_which_draws_itself - part titled Create a custom control which draws itself

The code consists of the unit holding the element (TRPreview, trimed out all the unnecessary bits) and bits from main unit, which is supposed to put all that on the form. Please remember that I come from a very different background and the code is just a boilerplate. Having said that though, I'll be happy if anyone points obvious wrong approach to the subject.

Code: Pascal  [Select][+][-]
  1. unit RPreview;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6. uses
  7.   Classes, Sysutils, REngine, Controls, graphics, lcltype;
  8.  
  9. type
  10.     TRPreview = class(TCustomControl)
  11.       private
  12.         _mprod: Extended;
  13.         _rprod: Extended;
  14.         _owned: Boolean;
  15.         _type: ReactorType;
  16.         _props :TReactorProperties;
  17.         _img:  TPicture;
  18.  
  19.        public
  20.          constructor Create(AOwner:TComponent);
  21.          procedure Free;
  22.          procedure Paint; override;
  23.          procedure EraseBackground(DC: HDC); override;
  24.     end;
  25.  
  26. implementation
  27.  
  28. constructor TRPreview.Create(AOwner:TComponent);
  29. begin
  30.   inherited Create(AOwner);
  31.   _mprod:=0;
  32.   _rprod:=0;
  33.   _owned:=false;
  34.   _type:=rtNone;
  35.   _img:=TPicture.Create;
  36. end;
  37.  
  38. procedure TRPreview.setType(t:ReactorType);
  39. var
  40.   res: String;
  41. begin
  42.   if t=rtNone then Exit;
  43.   _type:=t;
  44.   // cut out out of context code
  45.   _img.LoadFromResourceName(HINSTANCE,res,TPortableNetworkGraphic);
  46. end;
  47.  
  48. procedure TRPreview.Free;
  49. begin
  50.   _img.Free;
  51.   inherited Free;
  52. end;
  53.  
  54. procedure TRPreview.Paint;
  55. var
  56.   b: TBrush;
  57.   p: TPen;
  58. begin
  59.   if _type=rtNone then exit;
  60.   b:=TBrush.Create; p:=TPen.Create;
  61.   //p.Width:=0;
  62.   b.Color:=clBlack;
  63.   b.Style:=bsSolid;
  64.   //Canvas.Pen:=p;
  65.   Canvas.Brush:=b;
  66.   Canvas.FillRect(Left+4,Top+4,Left+Width-4,Top+Height-4);
  67.   //Canvas.Draw(5,5,_img.Bitmap);
  68.   //p.Width:=5;
  69.   //p.Color:=clGray;
  70.   //b.Style:=bsClear; b.Color:=clNone;
  71.   //Canvas.Pen:=p;
  72.   //Canvas.Brush:=b;
  73.   //Canvas.RoundRect(Top,Left,Width,Height,5,5);
  74.   b.Free; p.Free;
  75.   Inherited Paint;
  76. end;
  77.  
  78. procedure TRPreview.EraseBackground(DC: HDC);
  79. begin
  80.      //Inherited EraseBackground(DC);
  81. end;
  82.  
  83. end.
                                     
And the way it's being processed on the main form:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. var
  3.   t: ReactorType;
  4.   dx, dy, cx: Integer;
  5. begin
  6.   bkg:=TPicture.Create;
  7.   dx:=(Width div 2)-(2*180)-90; dy:=7*25; cx:=1;
  8.   bkg.LoadFromResourceName(HInstance,'BACKGROUND',TPortableNetworkGraphic);
  9.   brush.Bitmap:=bkg.Bitmap;
  10.   Canvas.FloodFill(0,0,clDefault,fsSurface);
  11.   Engine:=TEngine.Create;
  12.   for t:=rtIsland to rtContinent do begin
  13.     if t=rtNone then continue;
  14.     boxes[t]:=TRPreview.Create(Self);
  15.     boxes[t].Parent:=Self;
  16.     boxes[t].Width:=170; boxes[t].Height:=170;
  17.     boxes[t].Left:=dx; boxes[t].Top:=dy;
  18.     boxes[t].RType:=t;
  19.     boxes[t].DoubleBuffered:=true;
  20.     cx+=1;
  21.     If cx=6 then begin
  22.       dx:=(Width div 2)-(2*180)-90;
  23.       dy+=180;
  24.     end else dx+=180;
  25.   end;
  26. end;                          
  27.  

"boxes" is an array of TRPreview.

The result I am getting is all the boxes are square, with a gray background. What am I doing wrong?

Also, closing the program (compiled in debug mode, no optimisations) takes about half a second - that's normal?

My OS is Debian 12, all development packages come from a stable repository. Can also test on Windows and macOS if needed.

Many thanks in advance!

VisualLab

  • Hero Member
  • *****
  • Posts: 639
Re: Custom self drawn component issue
« Reply #1 on: February 06, 2025, 06:13:15 pm »
Well, you write your program's code, so you decide how you do it. However, if I were to give you a hint, it would be when programming in Object Pascal:
  • some C++ experience and knowledge may be useful to you,
  • "forget" that you know Python - it won't be useful to you.
In other words, the knowledge you have in C++ may be useful to a large extent. Object Pascal has (for example): a type system, classes, division of sections in classes into private, protected and public, programmer types (arrays, structures, enumerated types, and even more: e.g. collective type, truncated type), constants and variables, overloading of subroutines and methods, virtual and abstract methods, etc. What Object Pascal has that C++ does not: properties and events, virtual constructors and destructors.

However, the experience (knowledge) associated with using Python will be a big obstacle. Python is too far removed from typical imperative and object-oriented languages ​​(to put it bluntly: it is too primitive and poorly designed).

As for Object Pascal coding - you don't have to use underscores in field names in classes. If you look at the source code of FCL or LCL libraries, fields usually have names prefixed with the letter "F" (like : field). It's just a convention, but quite readable and aesthetic. Of course, the compiler doesn't care about this. But such code is easier to read. These underscores are ugly "pythonisms" (resulting from a poorly designed language - Guido's weird ideas). It's a language that's suitable for writing "quick" and one-time scripts, but it's definitely not suitable for creating window programs.

I created a simple example with a project of my own control based on the TCustomControl class. The project contains a module with the source code of the control and a form (window) used to test the control. The project was created in Lazarus 3.4 and compiled under Windows 10 64-bit. Under Linux, there may (but do not have to) be some (unpleasant) surprises, especially if it is a GUI using GTK.

wp

  • Hero Member
  • *****
  • Posts: 12682
Re: Custom self drawn component issue
« Reply #2 on: February 06, 2025, 06:29:29 pm »
I did not understand what's wrong. Square boxes? Well - that's the way you created them: Width = 170 and Height = 170. Gray background? No idea... Because you show only part of your code. Even if I try to set up a project from your snippets I cannot compile because unit REngine is missing.

When you show us code here, always ask yourself: If I were somebody else who has no idea of what I was doing, would I understand the post? Would I be able to compile the code shown? Because the compiler is an excellent tool to point to errors.

Artur W.

  • New member
  • *
  • Posts: 9
Re: Custom self drawn component issue
« Reply #3 on: February 08, 2025, 12:36:58 am »
Good evening Gentlemen,
Thank you for taking time to reply. Since I posted I took time to look around the forum a bit deeper than search for interesting keywords. I don't like to install stuff that's not in official repository but made use of the advice found here and there - installed everything from the SF. So now it's still FPC 3.2.2 but Lazarus is 3.8 - looks better, seem to work better but nothing changed in terms of how my code is rendered.

On that note:
Well - that's the way you created them: Width = 170 and Height = 170. Gray background? No idea... Because you show only part of your code. Even if I try to set up a project from your snippets I cannot compile because unit REngine is missing.

When you show us code here, always ask yourself: If I were somebody else who has no idea of what I was doing, would I understand the post? Would I be able to compile the code shown? Because the compiler is an excellent tool to point to errors.

I actually created RoundRect but more on that later.
Different forums have quite a bit different styles and people in them, I'll adjust my habits.


As for Object Pascal coding - you don't have to use underscores in field names in classes.

I merely used underscored as they stand out (for me) but happy to go with the flow.

As for my main hurdle - I created a single unit for Lazarus to allow anyone willing to try out. It should render a black form with a single _rounded_ button in the middle. And a frame. All I have is white box.

Code: Pascal  [Select][+][-]
  1. unit form;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5.   Classes, Sysutils, Forms, Controls, Graphics, Dialogs, LCLType;
  6.  
  7. type
  8.     TPreviewBtn = Class(TCustomControl)
  9.     public
  10.       procedure Paint; override;
  11.       procedure EraseBackground(DC: HDC); override;
  12.   end;
  13.  
  14.   Tform1 = class(Tform)
  15.     procedure Formcreate(Sender: Tobject);
  16.     private
  17.       fbtn: TPreviewBtn;
  18.   end;
  19.  
  20. var
  21.   Form1: Tform1;
  22. implementation
  23. {$R *.lfm}
  24.  
  25. procedure TPreviewBtn.Paint;
  26. var
  27.   s: TTextStyle;
  28. begin
  29.      Canvas.Brush.Color:=clGreen; Canvas.Brush.Style:=bsSolid;
  30.      Canvas.Pen.Color:=clBlue; Canvas.Pen.Width:=5; Canvas.Pen.Style:=psSolid;
  31.      Canvas.RoundRect(Left+1,Top+1,Left+Width-1,Top+Height-1,5,5);
  32.      s:=Canvas.TextStyle;
  33.      s.Layout:=tlCenter; s.Alignment:=taCenter;
  34.      Canvas.TextRect(GetClientRect,0,0,Caption,s);
  35. end;
  36.  
  37. procedure TPreviewBtn.EraseBackground(DC: HDC);
  38. begin
  39.      //Inherited EraseBackground(DC);
  40. end;
  41.  
  42. procedure Tform1.Formcreate(Sender: Tobject);
  43. begin
  44.   Brush.Color:= $000000;
  45.   fbtn:=TPreviewBtn.Create(Self);
  46.   fbtn.Parent:=Self;
  47.   fbtn.Top:=Height div 2; fbtn.Left:=Width div 4; fbtn.Width:=Width div 2;
  48.   fbtn.Height:=Height div 10;
  49. End;
  50. end.
  51.  

I hope that is more clear. Please let me know if attaching a zip with a whole sample project is more welcome?

As for events mentioned earlier - I worked with similar concept under C++ Qt framework (it is called signal/slot mechanism there), will definitely try to see how it is done in FP. That and databases (sqlite, pg).

In the meantime though, I'd like to understand why RoundRec produces square...

Josh

  • Hero Member
  • *****
  • Posts: 1363
Re: Custom self drawn component issue
« Reply #4 on: February 08, 2025, 11:17:09 am »
have u tried increasing the radius, 5 is small

The best way to get accurate information on the forum is to post something wrong and wait for corrections.

VisualLab

  • Hero Member
  • *****
  • Posts: 639
Re: Custom self drawn component issue
« Reply #5 on: February 09, 2025, 12:25:34 pm »

As for Object Pascal coding - you don't have to use underscores in field names in classes.

I merely used underscored as they stand out (for me) but happy to go with the flow.

Your code - so you decide.

As for my main hurdle - I created a single unit for Lazarus to allow anyone willing to try out. It should render a black form with a single _rounded_ button in the middle. And a frame. All I have is white box.

Well, I misunderstood your intentions. I thought it was related to chemical engineering. But as wp rightly pointed out, you provided too little input information :)

I hope that is more clear. Please let me know if attaching a zip with a whole sample project is more welcome?

If the project consists of a dozen or so modules (each 1000-1500 lines of code), then send it.

As for events mentioned earlier - I worked with similar concept under C++ Qt framework (it is called signal/slot mechanism there), will definitely try to see how it is done in FP. That and databases (sqlite, pg).

Well, yes. In C++ classes don't have properties and events. So Qt creators had to "push" their ideas through the C++ compiler using a preprocessor. Moreover, handling events causes more CPU overhead and memory usage (because events are objects). In Object Pascal, events are pointers to procedures/functions.

cdbc

  • Hero Member
  • *****
  • Posts: 1953
    • http://www.cdbc.dk
Re: Custom self drawn component issue
« Reply #6 on: February 09, 2025, 12:50:10 pm »
Hi
To elaborate on @VisualLab's sound advice...
In Object-Pascal, an event is a /fancy/ callback func/proc, namely a 'Method of object'.
It looks like this:
Code: Pascal  [Select][+][-]
  1. TMethod = record
  2.   Code: codepointer; { a procedural pointer to a function / procedure }
  3.   Data: pointer; { a pointer to the class, to which it belongs -- Self }
  4. end;
  5.  
In your event-handlers, you can access the underlying class with 'Self'.
Many events send themselves (class ref / self) along in the '(Sender)' parameter, which you can then query with 'is' and use with 'as'.
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 3.6 up until Jan 2024 from then on it's both above &: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 4.99

Artur W.

  • New member
  • *
  • Posts: 9
Re: Custom self drawn component issue
« Reply #7 on: February 10, 2025, 11:55:57 am »
Thank you for the insight, Gentlemen.

I managed to progress with my understanding of how things are done however RoundRect is still producing just a rectangle. I'll try to build it from the path using lines and arcs, then test both approaches on Windows.
Also, for the reason I still don't understand, painting directly on Canvas in TCustomControl descendant doesn't work properly, any progress I made was by painting on TBitmap. I don't say it is wrong, just wasn't expecting.

Anyway, I'll do some experimenting and reading documentation and will post later what I found. There has to be a way to make it work :)

Thank you for all the explanations and guidance so far!

cdbc

  • Hero Member
  • *****
  • Posts: 1953
    • http://www.cdbc.dk
Re: Custom self drawn component issue
« Reply #8 on: February 10, 2025, 12:21:18 pm »
Hi
Quote
Also, for the reason I still don't understand, painting directly on Canvas in TCustomControl descendant doesn't work properly
For persistent painting, you should do your painting in said component's 'OnPaint' event-handler.
Otherwise, every time the component is told to refresh itself, it will erase your drawings...
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 3.6 up until Jan 2024 from then on it's both above &: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 4.99

Artur W.

  • New member
  • *
  • Posts: 9
Re: Custom self drawn component issue
« Reply #9 on: February 10, 2025, 03:57:00 pm »
Have I misunderstood this tutorial page? https://wiki.freepascal.org/Developing_with_Graphics#Create_a_custom_control_which_draws_itself

Quote
Create a custom control which draws itself

Creating a custom control has the advantage of structuring your code and you can reuse the control. This approach is very fast, but it can still generate flickering if you don't draw to a TBitmap first and then draw to the canvas. On this case there is no need to use the OnPaint event of the control.
 

cdbc

  • Hero Member
  • *****
  • Posts: 1953
    • http://www.cdbc.dk
Re: Custom self drawn component issue
« Reply #10 on: February 10, 2025, 04:14:37 pm »
Hi
No, sorry mate, you haven't, I was expressing a more general advice towards drawing persistence...
You should definitely try to follow the tutorial as well as possible.
It's written for that purpose  ;)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 3.6 up until Jan 2024 from then on it's both above &: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 4.99

Artur W.

  • New member
  • *
  • Posts: 9
Re: Custom self drawn component issue
« Reply #11 on: February 10, 2025, 04:33:41 pm »
Cool, cool.

Thank you :)

Artur W.

  • New member
  • *
  • Posts: 9
Re: Custom self drawn component issue
« Reply #12 on: February 11, 2025, 01:52:24 pm »
Hello again!
The test code works flawlesly under Windows... I have not checked macOS yet but that's the least of my concerns.
That brings up two questions really:

1) what is general advise in a case like this, where Linux/GTK frontend is concerned?
2) is it something I should file a bug report about (RoundRect not working properly under GTK)?

EDIT:
My intention is for the code to run on Win, Lin and Mac platforms with a minimum amount of {$IFDEF} variations in that regard. I don't mind developing on any (although the way Lazarus works under mac is a bit abysmal).
So my so far intended course of action is to continue on Debian (as it proves to be problematic) while occasionally verifying on Windows and macOS.
Ad1) - the question really boils down to a choice: find a workaround that would work with GTK (And it most likely will work on Win and Mac) or forget it and rely on platform conditional.

Thanks,
Artur
« Last Edit: February 11, 2025, 02:27:49 pm by Artur W. »

VisualLab

  • Hero Member
  • *****
  • Posts: 639
Re: Custom self drawn component issue
« Reply #13 on: February 11, 2025, 09:26:20 pm »
Only now (after a few days) I noticed that in my first answer I attached a screenshot but forgot to attach an example of a class based on TCustomControl along with a simple test window that uses this class. So I'm attaching this project. It includes the overridden Paint method of the ancestor.


1) what is general advise in a case like this, where Linux/GTK frontend is concerned?

What do you mean by "Linux/GTK frontend"? Do you mean GUI? I'm asking because the division into "front-end" and "back-end" is rather used by people who create websites.

2) is it something I should file a bug report about (RoundRect not working properly under GTK)?

I don't know enough about the nuances of GTK, so I can't help there.



EDIT: The previous attached project had a bug (defect) because the overridden Paint method was placed in the public section of the created class. Meanwhile, it should be in the protected section. In the new version, the sample class code is already corrected.
« Last Edit: February 11, 2025, 09:43:14 pm by VisualLab »

Artur W.

  • New member
  • *
  • Posts: 9
Re: Custom self drawn component issue
« Reply #14 on: February 11, 2025, 11:02:39 pm »
Thanks for that! I will download the code now and see the differences in outcome (if any).

In my defence - Paint as a public method comes from the tutorial page I linked more than once here:
Quote
type
  TMyDrawingControl = class(TCustomControl)
  public
    procedure EraseBackground(DC: HDC); override;
    procedure Paint; override;
  end;
so that's how I did it myself. Indeed, in the source and documentation both overridden methods are private. :/

I'll post an update as soon as I have any.
Many thanks :)

 

TinyPortal © 2005-2018