Custom self drawn component issue lazarus / 39-Graphics ---------------------------------------------------------------------- Artur W. · 2025-02-05 18:15 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit RPreview; {$mode ObjFPC}{$H+} interfaceuses  Classes, Sysutils, REngine, Controls, graphics, lcltype; type    TRPreview = class(TCustomControl)      private        _mprod: Extended;        _rprod: Extended;        _owned: Boolean;        _type: ReactorType;      _props :TReactorProperties;        _img:  TPicture;        public         constructor Create(AOwner:TComponent);         procedure Free;         procedure Paint; override;         procedure EraseBackground(DC: HDC); override;    end; implementation constructor TRPreview.Create(AOwner:TComponent);begin  inherited Create(AOwner);  _mprod:=0;  _rprod:=0;  _owned:=false;  _type:=rtNone;  _img:=TPicture.Create;end; procedure TRPreview.setType(t:ReactorType);var  res: String;begin  if t=rtNone then Exit;  _type:=t;  // cut out out of context code  _img.LoadFromRe sourceName(HINSTANCE,res,TPortableNetworkGraphic);end; procedure TRPreview.Free;begin  _img.Free;  inherited Free;end; procedure TRPreview.Paint;var  b: TBrush;  p: TPen;begin  if _type=rtNone then exit;  b:=TBrush.Create; p:=TPen.Create;  //p.Width:=0;  b.Color:=clBlack;  b.Style:=bsSolid;  //Canvas.Pen:=p;  Canvas.Brush:=b;  Canvas.FillRect(Left+4,Top+4,Left+Width-4,Top+Height-4);  //Canvas.Draw(5,5,_img.Bitmap);  //p.Width:=5;  //p.Color:=clGray;  //b.Style:=bsClear; b.Color:=clNone;  //Canvas.Pen:=p;  //Canvas.Brush:=b;  //Canvas.RoundRect(Top,Left,Width,Height,5,5);  b.Free; p.Free;  Inherited Paint;end; procedure TRPreview.EraseBackground(DC: HDC);begin     //Inherited EraseBackground(DC);end; end.                                    And the way it's being processed on the main form: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.FormCreate(Sender: TObject);var  t: ReactorType;  dx, dy, cx: Integer;begin  bkg:=TPicture.Create;  dx:=(Width div 2)-(2*180)-90; dy:=7*25; cx:=1;  bkg.LoadFromResourceName(HInstance,'BACKGROUND',TPor tableNetworkGraphic);  brush.Bitmap:=bkg.Bitmap;  Canvas.FloodFill(0,0,clDefault,fsSurface);  Engine:=TEngine.Create;  for t:=rtIsland to rtContinent do begin    if t=rtNone then continue;    boxes[t]:=TRPreview.Create(Self);    boxes[t].Parent:=Self;    boxes[t].Width:=170; boxes[t].Height:=170;    boxes[t].Left:=dx; boxes[t].Top:=dy;    boxes[t].RType:=t;    boxes[t].DoubleBuffered:=true;    cx+=1;    If cx=6 then begin      dx:=(Width div 2)-(2*180)-90;      dy+=180;    end else dx+=180;  end;end;                         "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 · 2025-02-06 17:13 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 · 2025-02-06 17:29 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. · 2025-02-07 23:36 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: Quote from: wp on February 06, 2025, 06:29:29 pm 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. Quote from: VisualLab on February 06, 2025, 06:13:15 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. 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit form;{$mode objfpc}{$H+}interfaceuses  Classes, Sysutils, Forms, Controls, Graphics, Dialogs, LCLType; type    TPreviewBtn = Class(TCustomControl)    public      procedure Paint; override;      procedure EraseBackground(DC: HDC); override;  end;   Tform1 = class(Tform)    procedure Formcreate(Sender: Tobject);    private    fbtn: TPreviewBtn;  end; var  Form1: Tform1;implementation{$R *.lfm} procedure TPreviewBtn.Paint;var  s: TTextStyle;begin     Canvas.Brush.Color:=clGreen; Canvas.Brush.Style:=bsSolid;     Canvas.Pen.Color:=clBlue; Canvas.Pen.Width:=5; Canvas.Pen.Style:=psSolid;     Canvas.RoundRect(Left+1,Top+1,Left+Width-1,Top+Height-1,5,5);     s:=Canvas.TextStyle;     s.Layout:=tlCenter; s.Alignment:=taCenter;   Canvas.TextRect(GetClientRect,0,0,Caption,s);end; procedure TPreviewBtn.EraseBackground(DC: HDC);begin     //Inherited EraseBackground(DC);end; procedure Tform1.Formcreate(Sender: Tobject);begin  Brush.Color:= $000000;  fbtn:=TPreviewBtn.Create(Self);  fbtn.Parent:=Self;  fbtn.Top:=Height div 2; fbtn.Left:=Width div 4; fbtn.Width:=Width div 2;  fbtn.Height:=Height div 10;End;end.  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 · 2025-02-08 10:17 have u tried increasing the radius, 5 is small ---------------------------------------------------------------------- VisualLab · 2025-02-09 11:25 Quote from: Artur W. on February 08, 2025, 12:36:58 amQuote from: VisualLab on February 06, 2025, 06:13:15 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. Quote from: Artur W. on February 08, 2025, 12:36:58 amAs 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 Quote from: Artur W. on February 08, 2025, 12:36:58 amI 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. Quote from: Artur W. on February 08, 2025, 12:36:58 amAs 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 · 2025-02-09 11:50 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i TMethod = record  Code: codepointer; { a procedural pointer to a function / procedure }  Data: pointer; { a pointer to the class, to which it belongs -- Self }end; 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 ---------------------------------------------------------------------- Artur W. · 2025-02-10 10:55 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 · 2025-02-10 11:21 Hi QuoteAlso, for the reason I still don't understand, painting directly on Canvas in TCustomControl descendant doesn't work properlyFor 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 ---------------------------------------------------------------------- Artur W. · 2025-02-10 14:57 Have I misunderstood this tutorial page? https://wiki.freepascal.org/D eveloping_with_Graphics#Create_a_custom_control_which_draws_itself QuoteCreate 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 · 2025-02-10 15:14 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 ---------------------------------------------------------------------- Artur W. · 2025-02-10 15:33 Cool, cool. Thank you ---------------------------------------------------------------------- Artur W. · 2025-02-11 12:52 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 ---------------------------------------------------------------------- VisualLab · 2025-02-11 20:26 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. Quote from: Artur W. on February 11, 2025, 01:52:24 pm1) 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. Quote from: Artur W. on February 11, 2025, 01:52:24 pm2) 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. ---------------------------------------------------------------------- Artur W. · 2025-02-11 22:02 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: Quotetype   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 ---------------------------------------------------------------------- Artur W. · 2025-02-11 22:09 Ok, it does create rounded edges as I wished. Again, than you for taking time to educate me, dig in my dirty code and time. I hugely appreciate that. Not it's time for me to dig through the differences, verify all the code against not only tutorial but docs and I'll write again - hopefully some closing words. Good night everyone (as it is late in the evening where I type from). A. ---------------------------------------------------------------------- VisualLab · 2025-02-11 22:18 Quote from: Artur W. on February 11, 2025, 11:02:39 pmso that's how I did it myself. Indeed, in the source and documentation both overridden methods are private. :/ No, I didn't mean your code Just my own. In answer #13 I originally attached the source code of the project, where the Paint method was placed in the public section of the class being created. After sending the answer with the attachment, I noticed this error in the Lazarus window with the source code. So I replaced the version of the code in the attachment with the correct one. ---------------------------------------------------------------------- Artur W. · 2025-02-18 11:37 Again, thank you everyone for your patience and contribution to my education All works as I wanted it to.