我想创建一个自定义控件,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;
Run Code Online (Sandbox Code Playgroud)
以上是在自定义控件中实现的,如下所示:
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;
Run Code Online (Sandbox Code Playgroud)
通过上面我可以看到绘制的渐变,我可以看到绘制了一个白色的矩形形状,但是在调整控件窗口大小时有很多闪烁(例如,当调整到客户端时),我曾想过用位图进行双缓冲但是如果可能更喜欢只使用画布.我也无法再使用常用控件画布绘制,如注释行所示TMyControl.Paint.
我在这里误解了一些基本的东西吗?我把它全部搞错了?我读过关键部分和线程池之类的东西,但它有点压倒性.我尝试使用Canvas.Lock和Canvas.UnLock,但无论何时调整大小都会闪烁,并且在Paint方法中创建线程后我无法在常规画布上绘制.
所以我的问题是如何正确实现在画布上绘制的线程?代码是否全部错误,我需要重新开始并以正确的方式实现它?我真的迷失了这一点并发现它相当混乱,我甚至尝试移动我在Paint方法中创建线程的截取WM_SIZE消息方法,这确实减少了闪烁有点但不完全,我担心我可能错过了更大的东西在这里所以请欣赏一些反馈和指导.
谢谢.
Ari*_*The 10
1)您不能以多线程方式使用VCL.它只是不是为它而设计的.用于多线程的普遍锁存器和锁定的开销将是巨大的,并且其好处 - 对于99%的应用来说无可估量的无限小.
2)无论如何,你不要以多线程的方式使用你的Canvas.看到你的代码:
procedure TCanvasThread.Execute;
begin
if Assigned(FOnThreadPaint) then
Synchronize(Sync);
Run Code Online (Sandbox Code Playgroud)
这是什么意思?这意味着"我不想做多线程,我想在单个主VCL线程中运行我的所有工作".
什么Synchronize电话呢?阅读文档,Synchronize其基本形式意味着"暂时停止此线程并以单线程方式完成工作".现在,如果所有和每个后台工作线程都在"阻止我并在单线程中完成我的工作" - 那就是它所说的.您只创建线程以立即停止它们,但所有工作都将传输到单个主VCL线程.你只分配了许多你不使用的资源.您创建了单线程应用程序,其中包含许多已创建的负载,仅用于停止的额外线程.你额外杀死了可预测性并创造了 - 谷歌这个词! - 竞争条件.你现在有几个"伪背景线程",但你永远无法分辨哪些会先工作,哪个会工作第二.
那么有什么选择呢?
3)首先,只有在没有其他选项的情况下才进行多线程处理.当你有大量的任务被100%隔离,而不是一个共享变量.
procedure TMyControl.Paint;
begin
TCanvasThread.Create(Canvas, OnClientPaint);
TCanvasThread.Create(Canvas, OnShapesPaint);
Run Code Online (Sandbox Code Playgroud)
违反了规则,你获取一个相同的Canvas变量并将其放入两个线程中.你不应该.如果你不能将线程分开以完全隔离 - 那么你很可能没有一个多线程的任务.
Oookay,我太严谨了,有些任务可以分享一些关于变量的小数量,在前提下任何对它们的访问都是明亮的仲裁,所以永远不会有2个以上的线程同时执行它.但对于任何新手,拇指规则就像我说的:100%隔离或没有多线程,甚至不是99%.
所以,通常你可以想要使用单个Canvas,这意味着你只能有一个线程来做它.Oookay.尝试使用一些更快的画布而不是标准的VCL画布.也许像http://graphics32.org.还有TCanvas实施Direct 2D和GDI+- 我不知道它们是否更快.或者来自http://torry.net/的一些其他2D图形库和类似的目录.
总而言之 - 在尝试制作慢速多线程应用程序之前 - 将时间和精力投入到快速的单线程应用程序中.
4)有时你真的可以将你的图片分成几层,比如Photoshop图层.这次你可以希望多线程.您创建SEVERAL不同的位图,每个线程一个.你用透明颜色填充它们.然后你让你的线程将他们需要的部分绘制到他们自己的位图中.然后在主线程中,你看到所有线程完成它们的工作,然后在单个主线程中,你将那些许多透明位图一个接一个地融合到目标形式的TPainBox画布上,然后按照正确的顺序进行.但即使如此,你仍然好把它们的股票TCanvas,并TBitmap和使用速度更快的库.如果不出意外,我从来没有TBitmap使用透明图像的VCL库存的可靠和快速工作,它们只是不是为真正的透明度而设计的.它一次又一次出现了一些意想不到的缺点和故障.
5)对于那些线程的另一件事,除了比赛之外,你只是没有权利在WM_PAINT事件之外的GDI窗口上绘画,或者VCL就你说它在你TWinControl的Paint方法之外绘制表格(或任何)时违反合同(或者OnPaint在基本Paint方法中调用的处理程序).这只是违反MS Windows法律的行为.您可以将一些数据缓存填充,计算或下载一些不可见的数据偏移到后台线程中.也许在极端情况下甚至将该数据呈现为那些垄断的每个线程的临时位图.但渲染表单本身,在其画布上绘制 - 只能在Paint/中严格执行OnPaint,并且不能卸载到Paint方法退出后运行的任何实体.渲染的控制流程应该全部在内部Paint,而不是外部.因此,线程在这里不适用:在Paint方法之外执行它们没有合法权利来触摸表单的画布.您必须阅读有关MS Windows GDI窗口和消息的一些教程以及失效 - 娱乐周期如何工作.
6)最后一件事,去找OmniThreadingLibrary并阅读你可以找到的关于它的所有教程和解释.你必须得到一个简单的想法 - 多线程总是很昂贵(总是比单线程程序效率低,如果计算每个处理器)并且只能将程序的某些部分提取到多线程中,而不是整个程序,并且任何工作中只有100%被隔离的部分是真正多线程的.无论你做什么,与彼此有任何关系的作品部分都不会100%多线程.换句话说,尽可能多地阅读OTL教程和常见问题解答来理解这个简单的想法:你不想在你的大部分时间里多线程.多线程是一种例外,只有在某些特定情况下才值得.当您怀疑是否需要多线程时 - 那么您需要单线程.当没有正常和合法的意思起作用时,你只会将多线程作为最后的机会.那是半开玩笑,但只有一半.