如何在窗体画布上绘制内容并在窗体上绘制控件?
我尝试以下方法:
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;
在绘制其他控件之前绘制矩形,因此它隐藏在控件后面(根据Delphi Docs,这是预期的行为)。
我的问题是如何绘制控件?
答案 0 :(得分:9)
不要在绘图处理程序中'使'无效'。 Invalidating导致WM_PAINT
被发送,这当然会开始全面处理油漆。即使您不移动鼠标,您发布的代码示例也会导致“OnPaint”事件一次又一次地运行。由于您的绘图取决于光标的位置,因此您可以使用'OnMouseMove'事件。但是你也需要截取其他窗口控件的鼠标消息。由于这个原因,下面的示例使用'ApplicationEvents'组件。如果您的应用程序有多个表单,则需要设置一种机制来区分您正在使用的表单。
另请参阅文档,VCL的Invalidate
使整个窗口无效。你不需要这样做,你绘制一个小矩形,你就知道你正在绘制的确切位置。只是使你画的地方和你画的地方无效。
对于绘制控件,实际上绘图部分很容易,但是你不能用提供的画布来做。表单具有WS_CLIPCHILDREN
样式,子窗口的表面将从更新区域中排除,因此您必须使用GetDCEx
或GetWindowDC
。正如评论中提到的'user205376',删除你绘制的内容有点棘手,因为你可以在多个控件上实际绘制一个矩形。但是api也有这样的捷径,正如你在代码中看到的那样。
我尝试对代码进行评论以便能够遵循,但跳过了错误处理。实际的绘画可以在'OnPaint'事件处理程序中,但是不会从'TWinControl'下降的控件在处理程序之后被绘制。所以它在WM_PAINT处理程序中。
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's 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's 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;
答案 1 :(得分:1)
应用程序主窗口无法绘制其他控制界面。控件定期绘制和擦除自身(基于控件“绘制周期”)
您的应用程序只能绘制允许应用程序执行此操作的控件。许多常用控件为应用程序提供了灵活性,可以通过控件自定义绘制技术自定义控件外观。
答案 2 :(得分:1)
你不能。
在父窗口的顶部绘制控件。无论您在父窗口上绘制什么,都会在该窗口的控件后面看到。目前尚不清楚为什么你需要做这样的绘画;但是,也许你可以在窗体中创建一个透明控件并将其设置为前面,然后在其画布上绘制。这样你的绘图就会看到表单及其他控件的顶部,但这样用户就无法与表单上的其他控件进行交互,因为它们位于透明控件的后面。
答案 3 :(得分:1)
你不能这样做。您需要创建一个窗口控件(例如窗口)并将此窗口放在要“打开”的控件的顶部。然后你可以
使用控件复制表单的位图,并使用此位图作为此新控件的背景图像,或
使这个新窗口形状不规则,使其在一些不规则形状的区域外透明。
答案 4 :(得分:-2)
我做了一些涉及在我的表单上的组件周围绘制句柄的东西。
首先创建一个这样的消息:
Const
PM_AfterPaint = WM_App + 1;
编写一个处理消息的过程:
Procedure AfterPaint(var msg: tmsg); Message PM_AfterPaint;
Procedure AfterPaint(var msg: tmsg);
begin
{place the drawing code here}
ValidateRect(Handle, ClientRect);
end;
Validaterect将告诉Windows无需重新绘制表单。您的绘画将导致表单的一部分“无效”。 ValidateRect对windows说一切都是“验证”。
最后一步,您还需要覆盖绘制过程。
Procedure Paint; Override;
Procedure TForm1.paint;
Begin
Inherited;
PostMessage(Handle, PM_AfterPaint, 0, 0);
End;
因此,每次需要重新绘制表单(WM_Paint)时,它将调用祖先绘制并向消息队列添加AfterPaint消息。当消息处理过程中,AfterPaint被调用并且绘制你的东西并告诉Windows一切正常,防止另一个调用绘画。
希望得到这个帮助。