4

I'm trying to write a custom draw cell method for a TDBGridEh. The problem is when I change properties of pen, brush, ... the painting becomes messy. That's because the control does some extra painting itself after it calls the event handler. So I have to keep all props and then reset them when my own painting was finished.

I tried to create my own TControlCanvas and assign grid's one to it, but I get a run-time exception with message:

Cannot assign a TControlCanvas to a TControlCanvas

, that indicates the AssignTo method is not implemented for TControlCanvas nor for its ancestors. So my questions are:

  1. Why TControlCanvas does not have an AssignTo method? What is the problem?

  2. How can I keep and restore all properties of a TControlCanvas? And by that I mean something more convenient than creating TPen, TBrush, TFont, etc. .

saastn
  • 5,717
  • 8
  • 47
  • 78

2 Answers2

6

While TCanvas does not actually encapsulate these API functions, it is possible to use SaveDC and RestoreDC to do what you need. From the MSDN:

The SaveDC function saves the current state of the specified device context (DC) by copying data describing selected objects and graphic modes (such as the bitmap, brush, palette, font, pen, region, drawing mode, and mapping mode) to a context stack.

[...]

The RestoreDC function restores a device context (DC) to the specified state. The DC is restored by popping state information off a stack created by earlier calls to the SaveDC function.

A possible code sample:

uses
  Winapi.Windows;
...
var
  SavedDC: Integer;
begin
  SavedDC := SaveDC(Canvas.Handle);
  try
   // Painting code
  finally
    RestoreDC(Canvas.Handle, SavedDC);
  end;
end;

Edit:
I realized that this alone will likely not be the answer. This will handle the Device Context on the Windows side that is represented by the TCanvas /TControlCanvas object on Delphi's VCL side. But it will not alter any of the TFont, TBrush or TPen objects that the VCL holds. From tests it looks like that e.g. whenever Delphi uses the Canvas's Brush.GetHandle (FillRect, FrameRect), it is still the changed Brush.

So the best bet is to use SaveDC and RestoreDC in combination with storing and restoring Pen, Font and Brush like in the answer from Uwe Raabe.

nil
  • 1,320
  • 1
  • 10
  • 21
  • Tnx, it solved all brush issues. But I still have some problems with the pen. Finally I used `TRecall` descendants. – saastn Mar 27 '18 at 09:33
  • Hm, are you still creating a new temporary `TControlCanvas` when you paint? – nil Mar 27 '18 at 09:41
  • Does the Pen issue go away when calling `Canvas.Refresh;` after the `RestoreDC`? – nil Mar 27 '18 at 09:51
  • Yes! `Refresh` fixed it. What is the problem? – saastn Mar 27 '18 at 10:13
  • Just calling `Refresh` itself does the trick. I don't even need to call `SaveDC`-`RestoreDC`. I read the doc but I don't get much. – saastn Mar 27 '18 at 10:23
  • 1
    @saa [Here](http://edn.embarcadero.com/article/27786) is an article that attempts to explain refresh. Though I'm not quite convinced with neither the usage nor the explanation... – Sertac Akyuz Mar 27 '18 at 11:38
  • I wanted to link the article Sertac has found, but like him I am not convinced that is (still) right. I made a small test project - I must admit I did not test this answer practically - and there are issues. E.g `FillRect` will still paint with the old brush color after `RestoreDC`, but `TextRect` will use the correctly restored brush color. Looking into the code issue might be that the VCL still holds the previously used handles to the graphics objects and uses them (`FillRect` is calling `Winapi.Windows.FillRect(FHandle, Rect, Brush.GetHandle);`), maybe therefore the `TRecall` classes? – nil Mar 27 '18 at 11:52
  • I ended up with just calling `Refresh` yesterday. Today I was trying to use same paint method in another form, that had `TStringGrid` instead of `TDBGridEh`. There was painting issues back, but in a different way. So I tested `SaveDC`-`RestoreDC` in combination with `Refresh` but it didn't work. So, `TRecall` classes indeed! IThey work perfectly for both controls here. – saastn Mar 28 '18 at 10:28
5

Not sure if this fits what your expects, but there are TPenRecall, TBrushRecall and TFontRecall to save and restore the settings of these three properties in a semi-automatic way.

The handling is pretty simple: Create an instance of these classes with the corresponding properties as the parameter and do whatever you want with Pen, Brush and Font. In the end free those instances, which will restore the settings.

In combination with a TObjectList and some internal reference counting the effort needed to save and restore these canvas properties can be reduced to a one liner.

type
  TCanvasSaver = class(TInterfacedObject)
  private
    FStorage: TObjectList<TRecall>;
  public
    constructor Create(ACanvas: TCanvas);
    destructor Destroy; override;
    class function SaveCanvas(ACanvas: TCanvas): IInterface;
  end;

constructor TCanvasSaver.Create(ACanvas: TCanvas);
begin
  inherited Create;
  FStorage := TObjectList<TRecall>.Create(True);
  FStorage.Add(TFontRecall.Create(ACanvas.Font));
  FStorage.Add(TBrushRecall.Create(ACanvas.Brush));
  FStorage.Add(TPenRecall.Create(ACanvas.Pen));
end;

destructor TCanvasSaver.Destroy;
begin
  FStorage.Free;
  inherited;
end;

class function TCanvasSaver.SaveCanvas(ACanvas: TCanvas): IInterface;
begin
  Result := Self.Create(ACanvas);
end;

Usage:

procedure TForm274.DoYourDrawing(ACanvas: TCanvas);
begin
  TCanvasSaver.SaveCanvas(ACanvas);
  { Change Pen, Brush and Font of ACanvas and do whatever you need to do.
    Make sure that ACanvas is still valid and the same instance as at the entry of this method. }
end;
Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • Does the `TCanvasSaver.Create(ACanvas);` line work due to arc? In D2007 the destructor isn't called. Also, the `inherited;` line in the constructor doesn't compile - it has to be `inherited Create;` in D2007. – Uli Gerhardt Mar 27 '18 at 11:38
  • @UliGerhardt, Thanks for the hint! Seems I had an outdated version of the code in the clipboard. TCanvasSaver.SaveCanvas returns an interface which is implicitly hold until the method ends. – Uwe Raabe Mar 27 '18 at 11:45