I want to create a custom control derived from TCustomControl
which I will be overriding the Paint
method and drawing things such as a gradient background, graphics and shapes etc and then finally a grid over the top of it all.
I know all this is likely to be slow, so to optimize it all I thought about using threads, eg one thread to paint the background, one thread for painting the shapes and one thread for painting the grid, but I am not too confident in understanding and implementing it all correctly.
Through trial and error and looking at some thread examples (though I could never find any good thread painting examples) I managed to come up with the following which would be my general purpose thread class:
type
TCanvasThread = class(TThread)
private
FOnThreadPaint: TNotifyEvent;
FCanvas: TCanvas;
protected
procedure Execute; override;
procedure Sync;
public
constructor Create(Canvas: TCanvas; OnPaint: TNotifyEvent);
destructor Destroy; override;
property Canvas: TCanvas read FCanvas;
end;
constructor TCanvasThread.Create(Canvas: TCanvas; OnPaint: TNotifyEvent);
begin
inherited Create(False);
FreeOnTerminate := True;
FCanvas := Canvas;
FOnThreadPaint := OnPaint;
end;
destructor TCanvasThread.Destroy;
begin
inherited Destroy;
end;
procedure TCanvasThread.Execute;
begin
if Assigned(FOnThreadPaint) then
Synchronize(Sync);
end;
procedure TCanvasThread.Sync;
begin
FOnThreadPaint(Self);
end;
And the above is implemented into the custom control like so:
type
TMyControl = class(TCustomControl)
private
procedure OnClientPaint(Sender: TObject); // paint gradient
procedure OnShapesPaint(Sender: TObject); // paint shapes etc
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TMyControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Width := 600;
Height := 400;
end;
destructor TMyControl.Destroy;
begin
inherited Destroy;
end;
procedure TMyControl.OnClientPaint(Sender: TObject);
begin
GradientFillCanvas(TCanvasThread(Sender).Canvas, clSilver, clWhite, ClientRect, gdVertical);
end;
procedure TMyControl.OnShapesPaint(Sender: TObject);
begin
TCanvasThread(Sender).Canvas.Rectangle(50, 50, 100, 100);
end;
procedure TMyControl.Paint;
begin
TCanvasThread.Create(Canvas, OnClientPaint);
TCanvasThread.Create(Canvas, OnShapesPaint);
// implement other paint threads etc..
// TCanvasThread.Create(Canvas, OnGridPaint);
// ...
// using regular canvas drawing here seems to be blocked too?
end;
With the above I can see the gradient painted and I can see a white rectangle shape been drawn, but there is loads of flicker when resizing the controls window (eg when aligned to client), I had thought of double buffering with a bitmap but if possible would prefer to use just the canvas only. I also can no longer draw using the regular controls canvas as highlighted by the commented line in TMyControl.Paint
.
Have I misunderstood something basic here and I've implemented it all wrong? I read things like critical sections and thread pools etc but it is somewhat overwhelming. I experimented with Canvas.Lock and Canvas.UnLock but everything flickers regardless when resizing and I cannot draw on the regular canvas after creating my threads in the Paint
method.
So my question is how do I correctly implement threads for drawing on a canvas? Is the code all wrong and I need to start again and implement it in a correct way? Im really lost off at this point and finding it rather confusing, I even tried moving where I create the threads in the Paint
method to a intercepted WM_SIZE
message method which did reduce the flicker somewhat but not completely, I am worried I may have missed something bigger here so would appreciate some feedback and guidance please.
Thanks.