先擦除背景:

procedure TCustomForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
if not IsIconic(Handle) then inherited
else
begin
Message.Msg := WM_ICONERASEBKGND;
DefaultHandler(Message);
end;
end; procedure TWinControl.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
with ThemeServices do
if ThemesEnabled and Assigned(Parent) and (csParentBackground in FControlStyle) then
begin
{ Get the parent to draw its background into the control's background. }
DrawParentBackground(Handle, Message.DC, nil, False);
end
else
begin
{ Only erase background if we're not doublebuffering or painting to memory. }
if not FDoubleBuffered or
(TMessage(Message).wParam = TMessage(Message).lParam) then
FillRect(Message.DC, ClientRect, FBrush.Handle); // Brush的颜色事先读取好了
end; Message.Result := ;
end;

然后进行绘制(背景色已经事先存在,无论后面绘制了什么都不影响背景色,如果不绘制,就全部都是背景色):

procedure TCustomForm.WMPaint(var Message: TWMPaint);
var
DC: HDC;
PS: TPaintStruct;
begin
if not IsIconic(Handle) then
begin
ControlState := ControlState + [csCustomPaint]; // 模仿1
inherited; // 模仿2
ControlState := ControlState - [csCustomPaint];
end
else
begin
DC := BeginPaint(Handle, PS);
DrawIcon(DC, , , GetIconHandle);
EndPaint(Handle, PS);
end;
end;

inherited会调用:

procedure TWinControl.WMPaint(var Message: TWMPaint);
var
DC, MemDC: HDC;
MemBitmap, OldBitmap: HBITMAP;
PS: TPaintStruct;
begin
if not FDoubleBuffered or (Message.DC <> ) then
begin
if not (csCustomPaint in ControlState) and (ControlCount = ) then
inherited
else
PaintHandler(Message); // 走这里
end
else
begin
DC := GetDC();
MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom);
ReleaseDC(, DC);
MemDC := CreateCompatibleDC();
OldBitmap := SelectObject(MemDC, MemBitmap);
try
DC := BeginPaint(Handle, PS);
Perform(WM_ERASEBKGND, MemDC, MemDC);
Message.DC := MemDC;
WMPaint(Message);
Message.DC := ;
BitBlt(DC, , , ClientRect.Right, ClientRect.Bottom, MemDC, , , SRCCOPY);
EndPaint(Handle, PS);
finally
SelectObject(MemDC, OldBitmap);
DeleteDC(MemDC);
DeleteObject(MemBitmap);
end;
end;
end;

继续:

procedure TWinControl.PaintHandler(var Message: TWMPaint);
var
I, Clip, SaveIndex: Integer;
DC: HDC;
PS: TPaintStruct;
begin
DC := Message.DC;
if DC = then DC := BeginPaint(Handle, PS);
try
if FControls = nil then PaintWindow(DC) else
begin
SaveIndex := SaveDC(DC);
Clip := SimpleRegion;
for I := to FControls.Count - do
with TControl(FControls[I]) do
if (Visible or (csDesigning in ComponentState) and
not (csNoDesignVisible in ControlStyle)) and
(csOpaque in ControlStyle) then
begin
Clip := ExcludeClipRect(DC, Left, Top, Left + Width, Top + Height);
if Clip = NullRegion then Break;
end;
if Clip <> NullRegion then PaintWindow(DC); // 走这里
RestoreDC(DC, SaveIndex);
end;
PaintControls(DC, nil);
finally
if Message.DC = then EndPaint(Handle, PS);
end;
end;

TCustomForm有相应的覆盖函数:

procedure TCustomForm.PaintWindow(DC: HDC); // 模仿3
begin
FCanvas.Lock;
try
FCanvas.Handle := DC;
try
if FDesigner <> nil then FDesigner.PaintGrid else Paint; // 模仿4
finally
FCanvas.Handle := ;
end;
finally
FCanvas.Unlock;
end;
end;

Paint会调用:

procedure TCustomForm.Paint; // Paint是dynamic函数,也是虚函数
begin
if Assigned(FOnPaint) then FOnPaint(Self); // 巨变:这里直接调用程序员事件,而不是等着程序员覆盖Paint函数(那样做也可以,另外还可直接覆盖PaintWindow虚函数,所以一共有3种方法,即:覆盖OnPaint事件,覆盖PaintWindow虚函数,覆盖Paint虚函数)
end;

这个FOnPaint来自:

    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint stored IsForm;

它会调用我写的事件内容:

procedure TForm1.FormPaint(Sender: TObject);
begin
//
end;

即使为空,也丝毫不影响整个Form1的显示。也许像上面那样写会被编译器删除,那么我这样写:

procedure TForm1.FormPaint(Sender: TObject);
begin
tag := ;
end;

还是丝毫不影响整个Form1的显示。为什么会不影响呢?因为背景色提前就绘制在上面了,后面的OnPaint无论是否绘制,都不影响它的存在,顶多覆盖一小部分区域。比如:

procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Brush.Color := clRed;
Canvas.Rectangle(, , , );
end;

也就是覆盖了一个角,剩下的还是背景色。

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

这里测试了覆盖Paint函数,OnPaint的代码保留,但是效果只有左上角一个小绿块,而没有红色方块。如果加上inherited(IDE会自动帮你加上,也就是推荐使用),那么红的绿的方块都有,比较有意思:

procedure TForm1.Paint;
begin
// inherited;
Canvas.Brush.Color := clGreen;
Canvas.Rectangle(, , , );
end;

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

唯一有个问题是,InitInheritedComponent读取dfm的颜色以后,是什么时候把它赋值给FBrush.Color的?它与{$R *.dfm}是什么关系?

研究一下TForm.WMPaint过程(也得研究WM_ERASEBKGND)——TForm虽然继承自TWinControl,但是自行模仿了TCustomControl的全部行为,一共三种自绘的覆盖方法,比TCustomControl还多一种的更多相关文章

  1. 还在手动给css加前缀?no!几种自动处理css前缀的方法简介

    原文首发于个人博客:还在手动给css加前缀?no!几种自动处理css前缀的方法简介 我们知道在写css的时候由于要兼容不同厂商浏览器,一些比较新的属性需要给它们添加厂商前缀来兼容.移动端还好,基本只要 ...

  2. js中几种实用的跨域方法原理详解(转)

    今天研究js跨域问题的时候发现一篇好博,非常详细地讲解了js几种跨域方法的原理,特分享一下. 原博地址:http://www.cnblogs.com/2050/p/3191744.html 下面正文开 ...

  3. Java中有四种常见的Map实现方法

    在 HTML5 之前我们做图片预览主流做法有两种,第一种是通过 Flash 插件来做预览,第二种是 Ajax 实现的假预览,也就是说选择图片文件后,图片其实已经异步上传到服务器,服务器处理后返回图片路 ...

  4. js中几种实用的跨域方法原理详解

    这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被 ...

  5. [转]js中几种实用的跨域方法原理详解

    转自:js中几种实用的跨域方法原理详解 - 无双 - 博客园 // // 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同 ...

  6. js中几种实用的跨域方法原理详解【转】

    源地址:http://www.cnblogs.com/2050/p/3191744.html 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通 ...

  7. GOF提出的23种设计模式是哪些 设计模式有创建形、行为形、结构形三种类别 常用的Javascript中常用设计模式的其中17种 详解设计模式六大原则

    20151218mark 延伸扩展: -设计模式在很多语言PHP.JAVA.C#.C++.JS等都有各自的使用,但原理是相同的,比如JS常用的Javascript设计模式 -详解设计模式六大原则 设计 ...

  8. 再谈STM32的CAN过滤器-bxCAN的过滤器的4种工作模式以及使用方法总结

    1. 前言 bxCAN是STM32系列最稳定的IP核之一,无论有哪个新型号出来,这个IP核基本未变,可见这个IP核的设计是相当成熟的.本文所讲述的内容属于这个IP核的一部分,掌握了本文所讲内容,就可以 ...

  9. 五种WordPress防止垃圾评论方法-过滤垃圾评论提高WP运行效率

    WordPress貌似和垃圾评论是一对“孪生兄弟”,无论在国内还是国外的空间主机上搭建的Wordpress博客,无论Wordpress有多少流量多么低的权重,垃圾评论都会自动找上门来,假如有好几天没有 ...

随机推荐

  1. MyEclipse 引用其他项目及其jar包

    倘若在工作区有两个项目A和B,B项目引用A项目及其jar包(防止调用时A项目的方法出现NoClassFound),步骤如下: 在A项目上点右键看属性,点击Build Path--->Concon ...

  2. 错误处理try catch

    <?phpfunction inverse($x) { if (!$x) { throw new Exception('被除数不能为0'); } if ($x>31) { throw ne ...

  3. 通过sharedpreference两个程序共享数据

    一.整体工程图      二.SharePreferenceWriteActivity.java package org.ourunix.android.sharepreferencewrite; i ...

  4. UVALive 3989 Ladies&#39; Choice

    经典的稳定婚姻匹配问题 UVALive - 3989 Ladies' Choice Time Limit: 6000MS Memory Limit: Unknown 64bit IO Format:  ...

  5. activity入门2

    1.如何获取其他应用的包名和类名? 点击和查看logcat第一条信息2.第二步Intent intent = new Intent();intent.setClassName("com.an ...

  6. 嵌入式:使用dd命令制作烧写文件

    1. 使用dd命令制作烧写文件 环境: bootargs=mem=64M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtd ...

  7. 【转载】django在eclipse环境下建web网站

    一.创建一个项目如果这是你第一次使用Django,那么你必须进行一些初始设置.也就是通过自动生成代码来建立一个Django项目--一个Django项目的设置集,包含了数据库配置.Django详细选项设 ...

  8. Win7 和 MAC 系统通过VMware共享文件夹(简单又好用,几乎什么都不用设置)

    Win7是Server,Mac是Client,VMware上运行Mac系统 1.在VMware的Options菜单中选择Shared Folders选项 2.选择Always enabled选项 3. ...

  9. python中文utf8编码后是占3个字符,unicode汉字为2字节

    一个中文utf8编码后是占3个字符,所以求长度的函数可以这样写 def str_len(str): try: row_l=len(str) utf8_l=len(str.encode('utf-8') ...

  10. mysql备份数据库几种方法

    方法一 cmd 到mysql bin文件夹下用 例如以下命令 mysqldump --opt -h192.168.0.156 -uusername -ppassword --skip-lock-tab ...