【发布时间】:2017-05-24 14:28:35
【问题描述】:
我想创建一个从TCustomControl 派生的自定义控件,我将覆盖Paint 方法并绘制渐变背景、图形和形状等内容,最后在其顶部添加一个网格。
我知道这一切可能很慢,所以为了优化它,我想到了使用线程,例如一个线程绘制背景,一个线程绘制形状,一个线程绘制网格,但我不是对正确理解和实施这一切过于自信。
通过反复试验并查看一些线程示例(尽管我永远找不到任何好的线程绘制示例),我设法想出了以下这将是我的通用线程类:
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;
上面是这样实现到自定义控件中的:
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;
通过以上,我可以看到绘制的渐变,并且可以看到绘制了一个白色矩形,但是在调整控件窗口的大小时(例如,当与客户端对齐时)会出现大量闪烁,我曾想过使用双缓冲位图,但如果可能的话,宁愿只使用画布。我也不能再使用TMyControl.Paint 中注释行突出显示的常规控件画布进行绘制。
我是否误解了这里的一些基本内容并且我完全错误地实现了它?我阅读了诸如关键部分和线程池等内容,但它有点压倒性。我用 Canvas.Lock 和 Canvas.UnLock 进行了试验,但无论调整大小时一切都会闪烁,并且在 Paint 方法中创建线程后,我无法在常规画布上绘图。
所以我的问题是如何正确实现在画布上绘图的线程?代码都是错的,我需要重新开始并以正确的方式实现它吗?在这一点上我真的迷路了,发现它相当混乱,我什至尝试将我在 Paint 方法中创建线程的位置移动到截获的 WM_SIZE 消息方法,这确实在一定程度上减少了闪烁,但并没有完全减少,我担心我可能在这里遗漏了一些更大的东西,因此请提供一些反馈和指导。
谢谢。
【问题讨论】:
-
如果准备图形内容需要很长时间,请在线程中准备。但是当这样的准备完成后,将图形结果“传输”到可视化控件并保存在那里。最后,每当控件请求渲染(
Paint方法)时,绘制存储的图形。 -
@VictoriaMarotoSilva 这就是
TForm.DoubleBuffered := true所做的事情:-)(除了明显缺乏对鼠标点击和其他用户行为事件的即时行为类型反应) -
@Arioch'The,有点像 :) 这是关于自定义控件的。我在地图控件上进行了合作,这是在视觉控制中实际使用线程的一个很好的例子。您正在下载线程中的磁贴,将它们“传递”到控件以在它们“在家”时呈现。
-
@VictoriaMarotoSilva 瓷砖不重叠,因此您不会受到内存消耗和排序问题的影响。但是尝试制作“分层”地图,是否每个线程都渲染不是瓷砖而是所有地图,只是不同类别的对象。你会看到事情是如何变化的:-D
-
@Arioch'The,它是分层的。路由和自定义数据。我们在那里实现了相当复杂的逻辑(优先级)。备份 SQLite DB 中最常用的切片,不可见周围切片的内存缓存。瓷砖渲染和拖动以及其他元素都是动画的。但原则是所有“远程”数据源都是相同的——它们由工作线程获取并分派到控件,在那里它们被缓存以在控件请求时呈现。我们计划今年将此控件作为开源版本发布。
标签: delphi