Draw controls in a Delphi form

How can I draw something on the Forms canvas and above the controls on the form?

I try the following:

procedure TForm1.FormPaint(Sender: TObject); var x,y: Integer; begin x := Mouse.CursorPos.X - 10; y := Mouse.CursorPos.Y - 10; x := ScreentoClient(point(x,y)).X - 10; y := ScreenToClient(point(x,y)).Y - 10; Canvas.Brush.Color := clRed; Canvas.FillRect(rect(x, y, x + 10, y + 10)); Invalidate; end; 

The rectangle is drawn before other controls are drawn, so it is hidden behind the controls (this is the expected behavior according to Delphi docs).

My questions are: how can I draw the controls?

+4
source share
5 answers

Do not use "invalidate" in the paint handler. Invalidating causes the WM_PAINT to be sent, which, of course, starts the paint processing. Even if you do not move the mouse, the sample code you sent will raise the OnPaint event to fire again and again. Since your drawing depends on the position of the cursor, the OnMouseMove event is used for this. But you also need to intercept mouse messages for other controls using windows. The sample below uses the ApplicationEvents component for this reason. If your application will have more than one form, you need to establish a mechanism to distinguish which form you draw on.

Also see in the docs that VCL Invalidate invalidates the entire window. You do not need to do this, you draw a tiny rectangle, and you know exactly where you are drawing. Just invalid where you draw and where you painted.

As for drawing controls, part of the drawing is actually simple, but you cannot do this with the canvas provided. The forms received WS_CLIPCHILDREN , the surfaces of child windows will be excluded from the update area, so you will need to use GetDCEx or GetWindowDC . Since the β€œuser205376” mentioned in the comments, erasing what you painted is a bit more complicated, since you can actually draw one rectangle on more than one control. But api also has a shortcut for this, as you will see in the code.

I tried to comment a bit on the code to be able to follow, but skipped error handling. The actual picture may be in the OnPaint event handler, but controls that do not go down from the "TWinControl" are painted after the handler. So this is in the WM_PAINT handler.

 type TForm1 = class(TForm) [..] ApplicationEvents1: TApplicationEvents; procedure FormCreate(Sender: TObject); procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean); private FMousePt, FOldPt: TPoint; procedure WM_PAINT(var Msg: TWmPaint); message WM_PAINT; public end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin // no rectangle drawn at form creation FOldPt := Point(-1, -1); end; procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean); var R: TRect; Pt: TPoint; begin if Msg.message = WM_MOUSEMOVE then begin // assume no drawing (will test later against the point). // also, below RedrawWindow will cause an immediate WM_PAINT, this will // provide a hint to the paint handler to not to draw anything yet. FMousePt := Point(-1, -1); // first, if there already a previous rectangle, invalidate it to clear if (FOldPt.X > 0) and (FOldPt.Y > 0) then begin R := Rect(FOldPt.X - 10, FOldPt.Y - 10, FOldPt.X, FOldPt.Y); InvalidateRect(Handle, @R, True); // invalidate childs // the pointer could be on one window yet parts of the rectangle could be // on a child or/and a parent, better let Windows handle it all RedrawWindow(Handle, @R, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN); end; // is the message window our form? if Msg.hwnd = Handle then // then save the bottom-right coordinates FMousePt := SmallPointToPoint(TSmallPoint(Msg.lParam)) else begin // is the message window one of our child windows? if GetAncestor(Msg.hwnd, GA_ROOT) = Handle then begin // then convert to form client coordinates Pt := SmallPointToPoint(TSmallPoint(Msg.lParam)); windows.ClientToScreen(Msg.hwnd, Pt); FMousePt := ScreenToClient(Pt); end; end; // will we draw? (test against the point) if PtInRect(ClientRect, FMousePt) then begin R := Rect(FMousePt.X - 10, FMousePt.Y - 10, FMousePt.X, FMousePt.Y); InvalidateRect(Handle, @R, False); end; end; end; procedure TForm1.WM_PAINT(var Msg: TWmPaint); var DC: HDC; Rgn: HRGN; begin inherited; if (FMousePt.X > 0) and (FMousePt.Y > 0) then begin // save where we draw, we'll need to erase before we draw an other one FOldPt := FMousePt; // get a dc that could draw on child windows DC := GetDCEx(Handle, 0, DCX_PARENTCLIP); // don't draw on borders & caption Rgn := CreateRectRgn(ClientRect.Left, ClientRect.Top, ClientRect.Right, ClientRect.Bottom); SelectClipRgn(DC, Rgn); DeleteObject(Rgn); // draw a red rectangle SelectObject(DC, GetStockObject(DC_BRUSH)); SetDCBrushColor(DC, ColorToRGB(clRed)); FillRect(DC, Rect(FMousePt.X - 10, FMousePt.Y - 10, FMousePt.X, FMousePt.Y), 0); ReleaseDC(Handle, DC); end; end; 
+8
source

The main application window cannot draw on top of another control surface. Regulators periodically paint and wash themselves (based on the control cycle of the paint)

Your application can only use controls that allow the application to do this. Many common controls provide flexibility for applications to customize the appearance of controls using custom drawing methods.

+1
source

You can not.

Controls are drawn on top of their parent window. Everything that you draw on the parent window will be visible behind the controls above that window. It is not clear why you need to make such a drawing; however, perhaps you can create a transparent control inside the form and set it to the foreground, and then draw its canvas. Thus, your drawing will look on top of the form and other controls, but in this way, the user cannot interact with other controls in the form, because they are behind the transparent control.

+1
source

You cannot do this. You need to create a window control (for example, a window) and place this window on top of the controls you want to draw "on". Then you can

  • copy the bitmap of the form with the controls and use this bitmap as the background image of this new control or

  • make this new irregularly shaped window so that it is transparent outside some irregularly shaped area.

+1
source

I did something that attracted to draw pens around the components in my form here, what I did.

First create a message like this:

 Const PM_AfterPaint = WM_App + 1; 

Write a procedure to process the message:

 Procedure AfterPaint(var msg: tmsg); Message PM_AfterPaint; Procedure AfterPaint(var msg: tmsg); begin {place the drawing code here} ValidateRect(Handle, ClientRect); end; 

Validaterect will tell Windows that there is no need to redraw your form. Your painting will cause part of the form to be "invalid." ValidateRect says that everything is "checked" in windows.

You will also need the last step to override the drawing procedure.

 Procedure Paint; Override; Procedure TForm1.paint; Begin Inherited; PostMessage(Handle, PM_AfterPaint, 0, 0); End; 

Therefore, every time your form needs to be repainted (WM_Paint), it is called by the paint of the ancestor and adds the AfterPaint message to the message queue. When the message is a process, AfterPaint calls the call and draws your stuff and tells Windows that everything is in order, not allowing you to draw another call.

I hope for this help.

-2
source

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


All Articles