Recent

Author Topic: Changing Width/Height inside Paint method  (Read 1254 times)

simsee

  • Full Member
  • ***
  • Posts: 208
Changing Width/Height inside Paint method
« on: January 07, 2024, 07:49:04 pm »
I wrote a graphic component that consists of an arrow line. The arrow points are calculated in the component's Paint (overriden) method, then the PolyLine method draw all, within Paint. Still within Paint, according to the above calculation of the points, I calculate the Width and Height of the component, but this triggers a recursive loop on Paint. How can I change these properties dynamically inside Paint?

wp

  • Hero Member
  • *****
  • Posts: 12864
Re: Changing Width/Height inside Paint method
« Reply #1 on: January 07, 2024, 08:03:03 pm »
I think this concept is wrong. First set Width and Height - then draw. If you want to autosize the component override the method CalculatePreferredSize, it is called when AutoSize is set to true. If you need a canvas, for example to calculate some size within CalculatePreferredSize, create a temporary bitmap and do the calculation on the bitmap canvas.

simsee

  • Full Member
  • ***
  • Posts: 208
Re: Changing Width/Height inside Paint method
« Reply #2 on: January 07, 2024, 08:13:20 pm »
Thanks WP. I realize something is wrong with my approach. This is a component that is moved/resized at run time by the user. Okay, first I set the Width and Height, then I draw. But where do I set these two properties, if not in Paint?

jamie

  • Hero Member
  • *****
  • Posts: 6953
Re: Changing Width/Height inside Paint method
« Reply #3 on: January 07, 2024, 08:22:15 pm »
Put in a property in your control that contains the Polygone array.

When you set it, also calculate the size of the control at that point.

The OnPaint will then get called and you already have the Polygone data there, just use it without resizing.

The only true wisdom is knowing you know nothing

Thaddy

  • Hero Member
  • *****
  • Posts: 17158
  • Ceterum censeo Trump esse delendam
Re: Changing Width/Height inside Paint method
« Reply #4 on: January 07, 2024, 08:28:46 pm »
Did you not simply forget beginpaint.endpaint? If you forget that, it will cause a seemly endless loop. (It isn't but it causes many repaints to finish).
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

simsee

  • Full Member
  • ***
  • Posts: 208
Re: Changing Width/Height inside Paint method
« Reply #5 on: January 07, 2024, 08:39:11 pm »
My component is is a descendant of TGraphicControl.

I will try to draw inspiration from your suggestions, possibly showing you a small portion of the code. Thank you all.

simsee

  • Full Member
  • ***
  • Posts: 208
Re: Changing Width/Height inside Paint method
« Reply #6 on: January 07, 2024, 09:47:43 pm »
I'm sharing a small self-consistent subset of the graphics component I'm writing.

The code works, I don't ask for debugging. I would be happy if some forum user more experienced than me could take a look at it and give me some suggestions to improve it. Thanks in advance.

wp

  • Hero Member
  • *****
  • Posts: 12864
Re: Changing Width/Height inside Paint method
« Reply #7 on: January 08, 2024, 12:54:01 am »
A few thoughts:
  • Study the code of TShape or TArrow, they are very similar to your component.
  • Since your TGraphObject belongs to the LCL hierarchy, I would follow the basic conventions of the LCL as much as possible. There is no need to pass the bounds of the controls as a Rect parameter to the constructor. Better first create the control with its default properties, then change its properties, among them the size. In order to set the default size, either set Width and Height directly in the constructor, or - better - call SetInitialbounds which gets its values from an overridden GetControlClassDefaultSize (to be honest I don't remember the advantages of doing it this way, maybe related to LCL scaling? - but this is the way it is done all over the LCL)
Code: Pascal  [Select][+][-]
  1. constructor TGraphObject.Create(AOwner: TComponent);
  2. begin
  3.   if not (AOwner is TGraph) then
  4.     raise Exception.Create('TGraphObject can only be owned by TGraph');
  5.  
  6.   inherited Create(AOwner);
  7.  
  8.   with GetControlClassDefaultSize do
  9.     SetInitialBounds(0, 0, CX, CY);
  10.   ...
  11. end;
  12.  
  13. class function TGraphObject.GetControlClassDefaultSize: TSize;
  14. begin
  15.   Result.CX := 100;
  16.   Result.CY := 100;
  17. end;
  • I also don't see an immediate need to introduce a new constructor signature. I'd just use the AOwner:TComponent as Owner.
  • Setting the parent immediately in the constructor is a bit unusual, and I have a gut feeling that it may cause trouble sometimes, but maybe the final hierarchy of your classes requires that. But keep an eye on it.
  • Your original code in the constructor suggests that you want to draw the arrow between the top-left and bottom-right corners of the control's bounds. To do this, move this code into the SetBounds method which is called whenever the control size changes. This way the endpoints of the arrow move along with the control. I would call UpdateDraw from there to automatically recalculate the arrow. But then UpdateDraw must not call Setbounds again!
  • I can only speculate what your control is about to do, but I guess that you later want to be able to add further points in addition to the end points in the bounds corner. In this case, you must take precautions in SetBounds to not delete these additional points.
Code: Pascal  [Select][+][-]
  1. procedure TLink.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
  2. begin
  3.   if Length(FPoints) <= 2 then
  4.   begin
  5.     SetLength(FPoints, 2);
  6.     FPoints[0] := Point(ALeft, ATop);
  7.     FPoints[PointCount-1] := Point(ALeft+AWidth, ATop+AHeight);
  8.     SpikePoints := nil;
  9.   end;
  10.   UpdateDraw;
  11.   inherited;
  12. end;
  • Painting: Calling "inherited" from a TGraphControl.Paint only fires the OnPaint event, but beyond that does not draw anything. Therefore you must explicitely call your drawing code here. I'd suggest to do it like in TShape and delegate that to a virtual methode DrawToCanvas and pass the canvas as parameter. This way you can later send your drawing to a bitmap or the printer without much more additional drawing code.
Code: Pascal  [Select][+][-]
  1. procedure TGraphObject.Paint;
  2. begin
  3.   Canvas.Pen:=fPen;
  4.   Canvas.Brush:=fBrush;
  5.   Canvas.Font:=fFont;
  6.   DrawToCanvas(Canvas);
  7.   inherited Paint;
  8. end;  
  9.  
  10. procedure TLink.DrawToCanvas(ACanvas: TCanvas);
  11. var
  12.   TmpPoints : TPoints=nil;
  13.   ind : integer;
  14. begin
  15.   SetLength(TmpPoints,PointCount);
  16.   for ind:=0 to PointCount-1 do
  17.     begin
  18.       TmpPoints[ind].X:=fPoints[ind].X-Left;
  19.       TmpPoints[ind].Y:=fPoints[ind].Y-Top;
  20.     end;
  21.   ACanvas.Polyline(TmpPoints);
  22.  
  23.   if (SourcePoint<>TargetPoint) and (High(SpikePoints)=2) then
  24.     ACanvas.Polygon([
  25.       Point(SpikePoints[0].X-Left,SpikePoints[0].Y-Top),
  26.       Point(SpikePoints[1].X-Left,SpikePoints[1].Y-Top),
  27.       Point(SpikePoints[2].X-Left,SpikePoints[2].Y-Top)
  28.     ]);
  29. end;  
  • I am not sure if it's good idea to derive the "master class", TGraph, from TScrollbox because it might be too much specialization. I'd prefer a TPanel because then you might be able to add other controls to the "Graph" (a label to list point coordinates?). If you need scrollbar you can still pack then entire Graph into a scrollbox as outer parent - when the Graph is bigger than the scrollbox the scrollbars will appear automatically.
  • I added a modification of your project and component following these idea. There are two buttons on the form, one to add additional points, and one to increase the width of the link control. For the latter case, note that when the width exceeds some specific value part of the arrow shape is clipped at the bounds of the control. This cannot be avoided in the current approach in which TLink is a TControl. I guess it would be better not to implement TLink as a TGraphicControl, but just as a TComponent, and let the TGraph do all the drawing (which of course can and must be delegated to TLink again), but this way there is no invisible bounds rectangle around the link to clip part of its shape.

simsee

  • Full Member
  • ***
  • Posts: 208
Re: Changing Width/Height inside Paint method
« Reply #8 on: January 08, 2024, 02:45:12 am »
Dear WP, the code shown is a small portion of a much more complex context. Even though I eliminated 90% of the code, you has been able to understand the overall design.

Unfortunately I have little experience writing components and everything I know I learned from the wonderful chapter you wrote in the Lazarus Handbook.

Thad said, your observations are truly valuable and I will study them with great attention. You are always kind and very informative.

Thank you so much.
« Last Edit: January 08, 2024, 02:49:35 am by simsee »

 

TinyPortal © 2005-2018