Search code examples
windowsgdi+delphi-10-seattleinvalidation

Find out in WM_PAINT handler which region is being invalidated by InvalidateRgn


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?


Solution

  • Inside your WM_PAINT handler, you can use either:

    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.