ペイントハンドラーで「無効化」しないでください。無効にすると、WM_PAINT
が送信されます。これにより、もちろん、ペイントの処理がすべて開始されます。マウスを動かさなくても、投稿したコードサンプルにより、「OnPaint」イベントが何度も実行されます。描画はカーソルの位置に依存するため、これには「OnMouseMove」イベントを使用します。ただし、他のウィンドウコントロールのマウスメッセージもインターセプトする必要があります。以下のサンプルでは、この理由から「ApplicationEvents」コンポーネントを使用しています。アプリケーションに複数のフォームがある場合は、描画しているフォームを区別するメカニズムをデバイス化する必要があります。
また、ドキュメントで、VCLInvalidate
がウィンドウ全体を無効にすることも参照してください。あなたはそれをする必要はありません、あなたは小さな長方形を描いています、そしてあなたはあなたが描いている場所を正確に知っています。描画する場所と描画した場所を無効にするだけです。
コントロールへの描画に関しては、実際には描画部分は簡単ですが、提供されているキャンバスではそれを行うことはできません。フォームにはWS_CLIPCHILDREN
スタイルがあり、子ウィンドウのサーフェスは更新領域から除外されるため、GetDCEx
またはを使用する必要がありGetWindowDC
ます。コメントで「user205376」が言及しているように、実際には複数のコントロールに1つの長方形を描画できるため、描画したものを消去するのは少し注意が必要です。しかし、コードでわかるように、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;