My task is to create indicator with a background scale and a pointer. When pointer moves, I would like to redraw a part of background on its previous place and then draw a new pointer.
The pointer shape is defined as a polygon from which I have created a GDI+ region.
So I have 2 region objects: the old one and the new one.
Once in 50 milliseconds I call a following routine:
procedure TForm1._timerTick(Sender: TObject);
var
g: TGPGraphics;
mx: TGPMatrix;
hr: HRGN;
begin
if _newRegion <> nil then _newRegion.Free();
if _oldRegion <> nil then _oldRegion.Free();
_newRegion := _baseRegion.Clone();
_oldRegion := _baseRegion.Clone();
mx := TGPMatrix.Create();
try
mx.RotateAt(_angle, MakePoint(250.0, 120.0));
_oldRegion.Transform(mx);
mx.Reset();
Inc(_angle);
if _angle >= 360 then _angle := 0;
mx.RotateAt(_angle, MakePoint(250.0, 120.0));
_newRegion.Transform(mx);
finally
mx.Free();
end;
g := TGPGraphics.Create(Canvas.Handle);
try
hr := _oldRegion.GetHRGN(g);
try
InvalidateRgn(Handle, hr, False); //Restore background
finally
DeleteObject(hr);
end;
hr := _newRegion.GetHRGN(g);
try
InvalidateRgn(Handle, hr, False); //Draw new region
finally
DeleteObject(hr);
end;
finally
g.Free();
end;
end;
As you can see, I have no better idea than calling InvalidateRgn
twice: once for background restore and once for pointer drawing.
The WM_PAINT handler looks as follows:
procedure TForm1.Paint();
var
g: TGPGraphics;
mx: TGPMatrix;
brs: TGPSolidBrush;
begin
inherited;
g := TGPGraphics.Create(Canvas.Handle);
mx := TGPMatrix.Create();
brs := TGPSolidBrush.Create(MakeColor(255, 255, 0));
try
if _fullDraw then begin
_fullDraw := False;
end
else begin
g.IntersectClip(_oldRegion);
end;
g.DrawImage(_img, 0, 0);
if _newRegion <> nil then begin
g.ResetClip();
g.FillRegion(brs, _newRegion);
end;
finally
brs.Free();
mx.Free();
g.Free();
end;
end;
It always does two operations: restores background and draws the pointer.
If I perform InvalidateRgn
twice, then Paint will also be called twice resulting in four operations instead of two.
Is there some way to find out on Windows level inside of Paint method which region is being invalidated?
Inside your WM_PAINT
handler, you can use either:
GetUpdateRect()
or GetUpdateRgn()
before calling BeginPaint()
.
GetClipBox()
or GetClipRgn()
on the HDC
that BeginPaint()
returns (in VCL, GetClipBox()
is wrapped by the TCanvas.ClipRect
property for easier reading).
the PAINTSTRUCT.rcPaint
member that BeginPaint()
fills in upon output. This is the same rectangle that GetUpdateRect()
returns.
Either way will give you an update/clipping area within which the HDC
needs to be painted. Any drawing you do outside of that area will simply be discarded. So you can use this as an optimization to not waste effort painting anything that would just be discarded.
If I perform
InvalidateRgn
twice, then Paint will also be called twice resulting in four operations instead of two.
Not true. Window invalidations are cached and consolidated until the window is actually painted and validated. You can call InvalidateRect()
/InvalidateRgn()
as many times as you want, and the window will not be painted until you return control to the message loop so a WM_PAINT
message can then be generated (unless you force a paint by calling UpdateWindow()
or RedrawWindow()
). WM_PAINT
is a low-priority message, it is only generated by the message queue when the window has a non-empty Update Region and no other higher-priority messages are pending. The window is not validated until WM_PAINT
is processed (unless you force it by calling ValidateRect()
/ValidateRgn()
). See Invalidating and Validating the Update Region on MSDN for more details.