TPaintBox是一个图形控件,继承于TGraphicControl,并且只有聊聊几个函数和属性,主要就是Canvas和Paint函数,都在这里了:

  TPaintBox = class(TGraphicControl)
private
FOnPaint: TNotifyEvent;
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
property Canvas;
end;

总结:这么简单的类也做成了一个控件,并且堂而皇之的放在System一栏里,可见的这个控件虽然简单,但应该很实用,就是其它什么功能都没有,就提供了一个关键的Canvas属性和一个Paint函数,其主要目的就是为了提供一个绘画功能,程序员可以拿它画什么都可以,在功能设计上可以说是非常简单明了(题外话,也可以以此绘画功能为基础,做成多功能控件,比如做成和TLabel一样的功能等等,因为它太简单了,几乎等同于一个TGraphicControl)。

至于它的构造函数,除了增加一个csReplicatable风格以外,几乎就什么都没做了。所以主要还是要看它的Paint;函数,代码如下:

procedure TPaintBox.Paint;
begin
Canvas.Font := Font;
Canvas.Brush.Color := Color;
if csDesigning in ComponentState then
with Canvas do
begin
Pen.Style := psDash;
Brush.Style := bsClear;
Rectangle(0, 0, Width, Height);
end;
if Assigned(FOnPaint) then FOnPaint(Self);
end;

可以发现它只做了三件事情:
1.准备工作,设置Canvas的字体和画刷颜色
2.在设计期画出一个虚线的框,让程序员知道它的大小
3.调用程序员事件

看样子这个TPaintBox如果不增加程序员事件,它自己是什么事情都不会做的,所以接下来做实验,在空窗体上放一个PaintBox1,增加一段代码:

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
t: TRect;
begin
t.Top:=0;
t.Left:=0;
t.Right:=100;
t.Bottom:=100;
PaintBox1.Canvas.FillRect(t); // 使用画刷的颜色填充区域的颜色
end;

运行,没有效果。原因是它的默认color是clBtnFace,与Form1的颜色完全一致,所以运行看不出效果。

此时,通过IDE把Form1的颜色改成clAppWorkSpace,再运行还是看不出效果,屏幕一片灰暗,原因是IDE通过VCL里的代码察觉到了Form1的颜色变化,整个过程如下:

// 第一步,Form1作为一个TControl,给它设置颜色时会调用:
procedure TControl.SetColor(Value: TColor);
begin
if FColor <> Value then
begin
FColor := Value; // 简单设置类成员数据(不是类属性,后者会执行相关函数)
FParentColor := False;
Perform(CM_COLORCHANGED, 0, 0); // Form1颜色变化的一级入口
end;
end; // 第二步,连续执行三个CMColorChanged函数
procedure TCustomForm.CMColorChanged(var Message: TMessage);
begin
inherited;
if FCanvas <> nil then FCanvas.Brush.Color := Color;
end; procedure TWinControl.CMColorChanged(var Message: TMessage);
begin
inherited; // 这里使自己(Form1)的显示效果失效,一旦新的绘制消息到达,就可以根据新内容重绘了
FBrush.Color := FColor; // 然后设置类成员数据画刷的颜色
NotifyControls(CM_PARENTCOLORCHANGED); // 通知所有子控件,Form1作为它们的父控件,自己的颜色已经变化了
end; procedure TControl.CMColorChanged(var Message: TMessage);
begin
Invalidate; // 虚函数,所以要看是谁调用的这个函数。在这个例子中,此时当前控件是Form1,所以会调用TWinControl.Invalidate;这个过程不再展开。
end; // 第三步,把Form1自己的显示内容失效以后,还要继续通知所有子控件:
procedure TWinControl.NotifyControls(Msg: Word);
var
Message: TMessage;
begin
Message.Msg := Msg;
Message.WParam := 0;
Message.LParam := 0;
Message.Result := 0;
Broadcast(Message);
end; procedure TWinControl.Broadcast(var Message);
var
I: Integer;
begin
for I := 0 to ControlCount - 1 do
begin
Controls[I].WindowProc(TMessage(Message)); // 挨个通知子控件,并调用它们的相关消息函数,如果有的话
if TMessage(Message).Result <> 0 then Exit;
end;
end; // 第四步,结果PaintBox1作为子控件,还真收到了这个消息,并做出相应:
procedure TControl.CMParentColorChanged(var Message: TMessage); // Same as SetColor but doesn't set ParentBackground to False
procedure SetParentColor(Value: TColor);
begin
if FColor <> Value then
begin
FColor := Value; // 把自己的颜色改成父控件的颜色
FParentColor := False; // good 因为VCL库是非线程安全的,所以使了一个障眼法,临时不再接受新的颜色变化消息,但是一旦颜色变化完毕,马上就会改回来。
Perform(CM_COLORCHANGED, 0, 0); // 发消息调用函数,使得自己的颜色变化当场生效
end;
end; begin
// 注意,在此消息处理过程中,没有改变消息的result值,因此Broadcast函数里的消息for循环得以不断执行
if FParentColor then // 默认状态下就是如此
begin
if Message.wParam <> 0 then
SetParentColor(TColor(Message.lParam)) else
SetParentColor(FParent.FColor); // 调用子函数,把父控件的颜色设为自己的颜色,注意没有调用同名的类函数
FParentColor := True; // 前面执行消息使颜色生效后,马上改回来
end;
end; // 第五步,前面最后一句是PaintBox1执行Perform(CM_COLORCHANGED, 0, 0);,相当于执行:
procedure TControl.CMColorChanged(var Message: TMessage);
begin
Invalidate; // 会调用InvalidateControl函数使自己的显示内容失效,此处不再展开
end; // 第六步,WM_PAINT消息来了以后,会通过PaintBox1.Paint函数间接执行程序员的代码

总结:一旦在IDE里把Form1的颜色改变后,IDE会先把Form1的显示内容失效,然后设置Form1.FBrush.Color一个新的值,最后发消息挨个通知子控件,所有图形子控件默认都会响应(如果它的ParentColor设为True的话),因为VCL框架里在设置好了消息广播以及在TControl里就有CM_PARENTCOLORCHANGED相应的消息函数,所有图像子控件和Win控件都要继承,所以整个过程是必然的。一旦子控件响应,先设置自己的颜色,即PaintBox1.FColor := Value;,然后发消息让自己失效。这样就达到了父控件颜色变化,子控件颜色也跟着变的效果。如果是手动写代码改变父控件颜色,也是同样的执行流程。

但是通过IDE把Form1的颜色改成clAppWorkSpace后,就会调用上面整个过程,从而自动把PaintBox1的颜色也改成了与Form1一致的clAppWorkSpace,还能在IDE里当场生效,所以运行程序还是看不出效果。

那么手动把PaintBox1的颜色改成其它颜色呢?

procedure TForm1.Button4Click(Sender: TObject);
begin
PaintBox1.Canvas.Brush.Color:=clGreen;
PaintBox1.Invalidate;
end;

注意其中的Invalidate;语句,即使上一句设置Canvas的画刷颜色起作用,想要当场生效,就得加上这句话,否则得把Form1最小化然后最大化才能使之失效一次,才能看到相应的效果,岂不麻烦。只可惜即使加上了这句话,还是不行。那就没办法了,莫名惊诧之下就只能仔细研究它的Paint源代码了,于是发现Canvas.Brush.Color := Color;,即PaintBox1使用控件属性Color覆盖了它的Canvas画刷的颜色,看来问题就出在这里呀。于是再把测试语句改成:

procedure TForm1.Button4Click(Sender: TObject);
begin
PaintBox1.Color:=clRed;
end;

这回甚至都不用写Invalidate语句就能有效果了,其原因和设置Form1.Color当场有效果的原因是一致的,整个过程可以参考这里:
http://www.cnblogs.com/findumars/p/4117783.html

另外注意,PaintBox1.Color和PaintBox1.Brush.Color和PaintBox1.Canvas.Brush.Color这三个颜色本质上是三回事,其中PaintBox1.Color优先级最高,而且还会通过类属性对应的Set函数直接起作用。它们之间的关系只是有机会互相影响而已,那也得通过VCL的创造者手写的代码起作用才行,其相互关系可以参考上面那个帖子。

--------------------------------------------------------------------------

如果修改VCL代码,把TPaintBox.Paint;函数里的Canvas.Brush.Color := Color;语句去掉,那么执行
procedure TForm1.Button4Click(Sender: TObject);
begin
PaintBox1.Canvas.Brush.Color:=clRed;
end;
程序就会记住程序员在按钮里设置的颜色,然后不管是Invalidate;还是PaintBox1.Invalidate;都可使它当场生效。如果完全不写Invalidate,那么只有失效后,下次显示的时候,才会应用Button4里设置的红色,很有意思。其实源代码里写上这句话,意思无非就是说PaintBox1.Color等级最高,绘图的时候要服从它。另外就是注意,刷子的默认颜色是clWhite,注释掉那句话以后,什么都不做,马上就会在Form1上显示一个白色的PaintBox1,且永久有效。

TPaintBox的前世今生的更多相关文章

  1. 【调侃】IOC前世今生

    前些天,参与了公司内部小组的一次技术交流,主要是针对<IOC与AOP>,本着学而时习之的态度及积极分享的精神,我就结合一个小故事来初浅地剖析一下我眼中的“IOC前世今生”,以方便初学者能更 ...

  2. [C#] 回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性

    回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性 序 目前最新的版本是 C# 7.0,VS 的最新版本为 Visual Studio 2017 RC,两者都尚未进入正式阶段.C# 6.0 ...

  3. docker4dotnet #1 – 前世今生 & 世界你好

    作为一名.NET Developer,这几年看着docker的流行实在是有些眼馋.可惜的是,Docker是基于Linux环境的,眼瞧着那些 java, python, node.js, go 甚至连p ...

  4. Atitit 智能云网络摄像机的前世今生与历史 优点  密码默认888888

    Atitit 智能云网络摄像机的前世今生与历史 优点  密码默认888888 用户名admin  密码aaaaaa 网络摄像机是一种结合传统摄像机与网络技术所产生的新一代摄像机,它可以将影像通过网络传 ...

  5. 阿里开源消息中间件RocketMQ的前世今生-转自阿里中间件

    昨天,我们将分布式消息中间件RocketMQ捐赠给了开源软件基金会Apache. 孵化成功后,RocketMQ或将成为国内首个互联网中间件在Apache上的顶级项目. 消息一出,本以为群众的反应是这样 ...

  6. JavaScript的前世今生

    和CSS一样,JavaScript在各浏览器下并非完全一致,它所带来的兼容性问题时常困扰着我们,以至于现在“能否处理流行浏览器的兼容性问题”成为了检验一个程序员是否合格的标准之一.了解JavaScri ...

  7. 主成分分析PCA的前世今生

    这篇博客会以攻略形式介绍PCA在前世今生. 其实,主成分分析知识一种分析算法,他的前生:应用场景:后世:输出结果的去向,在网上的博客都没有详细的提示.这里,我将从应用场景开始,介绍到得出PCA结果后, ...

  8. SpringMVC源码剖析(二)- DispatcherServlet的前世今生

    上一篇文章<SpringMVC源码剖析(一)- 从抽象和接口说起>中,我介绍了一次典型的SpringMVC请求处理过程中,相继粉墨登场的各种核心类和接口.我刻意忽略了源码中的处理细节,只列 ...

  9. (转)word2vec前世今生

    word2vec 前世今生 2013年,Google开源了一款用于词向量计算的工具——word2vec,引起了工业界和学术界的关注.首先,word2vec可以在百万数量级的词典和上亿的数据集上进行高效 ...

随机推荐

  1. Web中Listener的创建

    使用Listener只需要两个步骤: 定义Listener实现类. 通过Annotation或在web.xml文件中配置Listener 实现Listener类 监听不同Web事件的监听器不相同,常用 ...

  2. C++中栈的出栈,入栈规则:A,B,C,D,E

    考题: 栈底至栈顶一次存放元素 ABCD 在第五个元素E入栈之前  栈中元素可以出栈,则出栈序列可能是_____a d___________. a.  ABCED b.  DBCEA   c.  CD ...

  3. 文件操作的openmode

    C中文件的openmode如下: r 只读 为输入打开一个文本文件 w 只写 为输出打开一个文本文件 a 追加 向文本文件尾添加数据 rb 只读 为输入打开一个二进制文件 wb 只写 为输出打开一个二 ...

  4. php编写验证码

    今天学习到了php登录时的验证码,验证码在我们平时的网站建设中是非常重要的,对于放置一些灌水机.脚本攻击是一个很好地策略. 下面是我写的代码: <?php session_start(); // ...

  5. 三级联动(ajax)

    <body> <div id="zhuti"></div> </body><script type="text/ja ...

  6. 第五周作业 关于C语言的问卷调查

    你对自己的未来有什么规划?做了哪些准备? 目前还不是很了解,我希望自己再毕业后可以在一家IT公司上班.  目前效果还不是很明显,只是对于专业的学习更加勤奋而已. 2.你认为什么是学习?学习有什么用?现 ...

  7. REST API 基于ACCESS TOKEN 的权限解决方案

    REST 设计原则是statelessness的,而且但客户端是APP时,从APP发起的请求,不是基于bowers,无法带相同的sessionid,所以比较好的方案是每次请求都带一个accesstok ...

  8. Net数值计算MathNet.Numerics类库

    一.Net自带的数值计算:System.Numerics 1.大整数BitInteger 方法:除数和余数.最大公约数 2.复数Complex 属性:实部.虚部.量值.相位 方法:共轭.倒数 二.Ma ...

  9. js java正则表达式替换手机号4-7位为星*号

    需求: 一个手机号13152461111,由于安全性,需要替换4-7位字符串为星号,为131****1111,那么有2中玩法,一种是前端隐藏,一种是后台隐藏. 1. 前台隐藏 <!DOCTYPE ...

  10. MYSQL注入天书之order by后的injection

    Background-9  order by后的injection 此处应介绍order by后的注入以及limit注入,我们结合less-46更容易讲解,(在less46中详细讲解)所以此处可根据l ...