What is the safest and most correct way to draw on a TFrame surface?

I have a frame in my project ( TFrame descendant) and want to draw something on it.

As I could see in the forums, a common way to do this is to override the PaintWindow method.

I tried this in a clean project:

 type TMyFrame = class(TFrame) private FCanvas: TCanvas; protected procedure PaintWindow(DC: HDC); override; public constructor Create(AOwner: TComponent); override; destructor Destroy(); override; end; implementation {$R *.dfm} constructor TMyFrame.Create(AOwner: TComponent); begin inherited; FCanvas := TCanvas.Create(); end; destructor TMyFrame.Destroy(); begin FCanvas.Free(); inherited; end; procedure TMyFrame.PaintWindow(DC: HDC); begin inherited; FCanvas.Handle := DC; FCanvas.Pen.Width := 3; FCanvas.Pen.Color := clRed; FCanvas.MoveTo(0, 0); FCanvas.LineTo(ClientWidth, ClientHeight); FCanvas.Pen.Color := clGreen; FCanvas.MoveTo(ClientWidth, 0); FCanvas.LineTo(0, ClientHeight); end; 

However, after placing my frame in the main form, the debugger never introduced this method until I turned on DoubleBuffered in the frame properties . Any ParentBackground value ParentBackground not affect the result.

Overriding the WM_PAINT handler also solves the problem:

 type TMyFrame = class(TFrame) protected procedure WMPaint(var Message: TWMPaint); message WM_PAINT; ... procedure TMyFrame.WMPaint(var Message: TWMPaint); begin inherited; FCanvas.Handle := GetDC(Handle); FCanvas.Pen.Width := 3; FCanvas.Pen.Color := clRed; FCanvas.MoveTo(0, 0); FCanvas.LineTo(ClientWidth, ClientHeight); FCanvas.Pen.Color := clGreen; FCanvas.MoveTo(ClientWidth, 0); FCanvas.LineTo(0, ClientHeight); ReleaseDC(Handle, FCanvas.Handle); end; 

this code always draws intersecting lines, no matter what values ​​were assigned to DoubleBuffered or ParentBackground .

But when I tried to use BeginPaint / EndPaint instead of GetDC / ReleaseDC , the problem returned:

 procedure TMyFrame.WMPaint(var Message: TWMPaint); var PS: PAINTSTRUCT; begin inherited; FCanvas.Handle := BeginPaint(Handle, PS); FCanvas.Pen.Width := 3; FCanvas.Pen.Color := clRed; FCanvas.MoveTo(0, 0); FCanvas.LineTo(ClientWidth, ClientHeight); FCanvas.Pen.Color := clGreen; FCanvas.MoveTo(ClientWidth, 0); FCanvas.LineTo(0, ClientHeight); EndPaint(Handle, PS); end; 

FCanvas.Handle is nonzero, but the result is an empty frame. In this case, installing DoubleBuffered or ParentBackground does not change anything.

Maybe I will call them wrong?

Now I use the WM_PAINT handler with GetDC / ReleaseDC , because I do not want to include DoubleBuffered in this frame. In addition, I am afraid that other programmers will accidentally disable DoubleBuffered after they put my shot in their projects and will have the same headache as mine.

But maybe there are safer and more correct ways to draw on the surface of the frame?

+4
source share
2 answers

I can duplicate your problem if I do not place any control over the test frame (this is also probably the reason why none of us can duplicate your problem - fi drop the control to visually verify that the frame is in shape).

The reason PaintHandler not called when there are no controls on it, and the reason when it is called when DoubleBuffered installed, although there are no controls on it, is just like the WM_PAINT TWinControl message TWinControl :

 procedure TWinControl.WMPaint(var Message: TWMPaint); var .. begin if not FDoubleBuffered or (Message.DC <> 0) then begin if not (csCustomPaint in ControlState) and (ControlCount = 0) then inherited else PaintHandler(Message); end else begin .. 

As you can see, when DoubleBuffered not installed and when there are no controls, PaintHandler not called (because there is nothing to draw: we are not a custom drawing (there is no csCustomPaint flag), and there are no controls to display). When the DoubleBuffered parameter is set, the following code path is WMPrintClient that calls WMPrintClient , which in turn calls PaintHandler .


If you intend to use the frame without any controls (although this is unlikely), the fix is ​​obvious (also reasonable when you know it), from the code snippet: include csCustomPaint in ControlState :

 type TMyFrame = class(TFrame) .. protected procedure WMPaint(var Message: TWMPaint); message WM_PAINT; .. procedure TMyFrame.WMPaint(var Message: TWMPaint); begin ControlState := ControlState + [csCustomPaint]; inherited; ControlState := ControlState - [csCustomPaint]; end; 

then the WM_PAINT inherited handler will call PaintHandler .


As for why drawing using BeginPaint / EndPaint in the WM_PAINT message handler doesn't seem to work, the reason is that the inherited call that precedes your drawing checks the update area. Check your rcPaint member PAINTSTRUCT after calling BeginPaint , you will find it (0, 0, 0, 0).

Since there is no invalidation area in this case, the OS simply ignores the following drawing calls. You can verify this by not paying attention to the client frame rectangle before drawing on the canvas:

 procedure TMyFrame.WMPaint(var Message: TWMPaint); var PS: PAINTSTRUCT; begin inherited; InvalidateRect(Handle, nil, False); // <- here FCanvas.Handle := BeginPaint(Handle, PS); FCanvas.Pen.Width := 3; FCanvas.Pen.Color := clRed; FCanvas.MoveTo(0, 0); FCanvas.LineTo(ClientWidth, ClientHeight); FCanvas.Pen.Color := clGreen; FCanvas.MoveTo(ClientWidth, 0); FCanvas.LineTo(0, ClientHeight); EndPaint(Handle, PS); end; 

and now you will see that your drawing takes effect. Of course, you can refuse to call inherited or invalidate only the part you draw on.

+4
source

It seems like he’s only called when in the designer

 procedure TCustomFrame.PaintWindow(DC: HDC); begin // Paint a grid if designing a frame that paints its own background if (csDesigning in ComponentState) and (Parent is TForm) then with TForm(Parent) do if (Designer <> nil) and (Designer.GetRoot = Self) and (not StyleServices.Enabled or not Self.ParentBackground) then Designer.PaintGrid end; 

The only way to make custom paint is to add WM_PAINT to your frame:

 TFrame3 = class(TFrame) protected procedure WMPaint(var Message: TWMPaint); message WM_PAINT; end; 
0
source

Source: https://habr.com/ru/post/1413481/


All Articles