来自万一的帖子:
http://www.cnblogs.com/del/archive/2008/04/27/1173658.html
的确做到了一行代码设置TForm控件的颜色(一点感想:Delphi程序员真幸福)。但真实的情况是,VCL框架在这个过程中做了大量的工作,经过多次消息的发送和响应,才达到了目的,大致顺序如下:

procedure TForm1.Button1Click(Sender: TObject);
begin
Self.Color := clRed;
end; procedure TControl.SetColor(Value: TColor);
begin
if FColor <> Value then
begin
FColor := Value;
FParentColor := False;
Perform(CM_COLORCHANGED, 0, 0); // 第一个消息,当前类和各祖先类简直是群起响应,但既然是虚函数嘛,入口函数还是当前类自己的消息函数
end;
end; procedure TCustomForm.CMColorChanged(var Message: TMessage);
begin
inherited;
if FCanvas <> nil then FCanvas.Brush.Color := Color; // 虽然这里把Canvas.Brush.Color的值给覆盖了,但它是用来专门绘图的,就当前效果来说,VCL框架使用的是FillRect API,而没有用到Canvas,所以不起作用
end; procedure TWinControl.CMColorChanged(var Message: TMessage);
begin
inherited; // 自己的颜色变化起效果,是通过这句话来实现的,它包含了一连串的执行过程。父控件变化完了再通知子控件,风格很强势。
FBrush.Color := FColor; // 这里,读取控件的颜色,然后给控件的Brush赋值
NotifyControls(CM_PARENTCOLORCHANGED); // 第二个消息,组建消息并传播,通知子控件,但没有任何子控件响应
end; procedure TControl.CMColorChanged(var Message: TMessage);
begin
Invalidate; // 虚函数,所以要看是谁调用的这个函数。这个例子中会调用TWinControl.Invalidate;
end; procedure TWinControl.Invalidate;
begin
// 注意,第二个参数即WParam是0,即要求API使自己失效,而不是仅仅做一个通知作用。
Perform(CM_INVALIDATE, 0, 0); // 第三个消息,还是要再次发消息,直到CM消息才能真正起作用,因为它统一了参数。在这里,虚函数的作用被弱化了,只起了一个包装的作用。
end; procedure TWinControl.CMInvalidate(var Message: TMessage);
var
I: Integer;
begin
if HandleAllocated then
begin
if Parent <> nil then
Parent.Perform(CM_INVALIDATE, 1, 0); // 第四个消息,递归,先通知父类(父类要提前通知)。Form1的Parent是Application,它没有响应。
if Message.WParam = 0 then
begin
// API,第二个参数为NULL的话,则重画整个客户区;第三个参数TRUE则背景重绘。
InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle)); // 总算初步达到目的,使Form1失效,后面还要自绘
{ Invalidate child windows which use the parentbackground when themed }
if ThemeServices.ThemesEnabled then
for I := 0 to ControlCount - 1 do
if csParentBackground in Controls[I].ControlStyle then // important
Controls[I].Invalidate;
end;
end;
end;

以上过程使Form1的画板失效(说到底,还是通过Form1.FCanvas.Brush起作用,类属性Color只是表象),接下去还有一个绘制Form1的过程。TForm1继承自TForm,TForm继承自TWinControl,而不是TCustomControl,而且响应WM_PAINT消息并覆盖了WMPaint函数,所以Windows会把WM_PAINT直接发给Form1,调用顺序如下:

TCustomForm.WMEraseBkgnd(var Message: TWMEraseBkgnd); // 区分正常情况和图标情况
TWinControl.WMEraseBkgnd(var Message: TWMEraseBkgnd); // 绘制背景色(相当于擦除了旧的背景)
TCustomForm.WMPaint(var Message: TWMPaint); // 区分正常情况和图标情况
TWinControl.WMPaint(var Message: TWMPaint); // 判断双缓冲和自绘,除了极少数Windows自带控件的包装(比如TEdit,TButton),都要执行PaintHandler自绘
TWinControl.PaintHandler(var Message: TWMPaint); // 先绘制当前win控件,比如Form1(有可能是剪裁后的剩余部分),然后绘制其所有图形子控件
procedure TCustomForm.PaintWindow(DC: HDC); // 使用WM_PAINT消息自带的DC句柄绘制Form1窗体
procedure TCustomForm.Paint; // 调用程序员事件,或者显示设计时的网格

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

而Form1的初始颜色在哪里设置呢?回答是没有在代码里设置,而是在dfm中设置了clBtnFace色,如果手动把它改成clRed,就立即就可以看到效果。这是一个空白窗体的dfm内容,一共14项:

object Form1: TForm1
Left = 0
Top = 0
Height = 282
Width = 418
Caption = 'Form1'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
end

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

另外我终于明白了,为什么我在TCustomForm里直接改源代码,却始终无法得到相应的效果:

constructor TCustomForm.CreateNew(AOwner: TComponent; Dummy: Integer);
begin
Color := clRed; // 这三句语句为什么都不起作用?
Brush.Color := clRed;
FCanvas.Brush.Color := clRed;
end;

因为TForm的创建顺序如下:

begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end. procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var
Instance: TComponent;
begin
Instance := TComponent(InstanceClass.NewInstance); // 经典,使用元类创建实例。分配内存
TComponent(Reference) := Instance;
try
Instance.Create(Self); // 这里相当于调用TForm.Create;
except
TComponent(Reference) := nil;
raise;
end;
if (FMainForm = nil) and (Instance is TForm) then
begin
TForm(Instance).HandleNeeded;
FMainForm := TForm(Instance);
end;
end;

而TForm.Create会先调用TForm.CreateNew;后调用InitInheritedComponent读取dfm文件,这样就相当于存储字dfm文件里的颜色覆盖了我手动指定的clRed颜色,这就是始终无法生效的原因。所以应该在TCustomForm.Create函数里的InitInheritedComponent语句之后写:
Color :=clBlue;
Brush.Color := clBlue;
就可以立刻生效。但是如果写:
Canvas.Brush.Color := clBlue;
则无法生效,原因是Canvas在这个过程中并没有被用到,而且这个赋值过早,它在TCustomForm.CMColorChanged函数里被类属性Color的值覆盖了,所以无法生效。

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

对于Form1.Color, Brush.Color, Canvas.Brush.Color三个颜色值之间的关系分析
如果把这两句:
Color :=clRed;
Brush.Color := clBlue;
写在TCustomForm.Create里面,无论哪句写在后面,都会按照后面一句的颜色来设置。但是其过程有所不同:
1. 如果Color :=clRed;写在后面,那么相当于调用了类属性,以及后面一系列变化,当执行TWinControl.CMColorChanged的时候,就会执行FBrush.Color := FColor;,相当于把FBrush.Color的值给覆盖了,前面那句话的效果就失效了。
2. 如果Brush.Color := clBlue;写在后面,在执行了前一句的效果以后,再把Brush.Color的值给简单覆盖掉了,前面那句话的效果自然就没有效果了。
总结:这两句话虽然都有相同的效果,但是执行过程可大不一样。使用Brush.Color := clBlue;这样更省事一些,因为它只是Delphi语言层面改变一个值,然后在刷新背景的时候供FillRect直接使用。如果使用Color :=clRed;其实分为2步,第一步是使整个Form1客户区失效,第二步是指Delphi语言层面改变Brush的值。这上面两步,都是在WM_ERASEBKGND消息和WM_PAINT消息来之前做的准备工作,这样一旦刷新消息来了就会立刻产生刷新的效果。

通过以上分析,我忽然注意到一个问题:如果直接执行Brush.Color := clBlue;,只是改变了控件画刷的颜色,并没有使客户区失效,那还有有效果吗?我试了一下:
procedure TForm1.Button2Click(Sender: TObject);
begin
Brush.Color := clGreen;
end;
点击按钮,Form1果然没有变换颜色的效果!这说明,虽然画刷颜色被改变了,但毕竟少了一个步骤,客户区没有失效,所以还是没效果。要等到下一次客户区失效,才能起效果。于是把窗口最小化,再恢复最大化,这样Form1客户区就变绿色了。而且以后也一直保持绿色。更有意思的是,用另外一个窗口(比如记事本)挡住Form1的部分客户区,然后移开,那么这部分客户区的颜色是绿色,其它部分仍然是红色!

而在constructor TCustomForm.Create里写上Brush.Color := clBlue;也会立刻生效,原因是Form1从未被显示过,所以第一次显示的时候,会自动发送擦除背景消息,此时画刷的颜色正是刚才设置的颜色,被FillRect API直接使用,所以能够立刻起作用!所以这是特殊情况,在一般情况下这样是不行的。
所以可以改成这样:
procedure TForm1.Button3Click(Sender: TObject);
begin
Invalidate;
Brush.Color := clGreen;
end;
这样和VCL框架的执行过程是一个意思,当然有效果。
再改成这样:
procedure TForm1.Button3Click(Sender: TObject);
begin
Brush.Color := clGreen;
Invalidate;
end;
也同样有效果,但其实我觉得这样写更合理。万事俱备了,再发消息做相应的动作,当然万无一失。

在TCustomForm.CMColorChanged函数里,虽然有:
if FCanvas <> nil then FCanvas.Brush.Color := Color;
但是这是专门使用Canvas画图的时候才使用。而此时,VCL使用的是FillRect API画出的效果,所以即使把这句话屏蔽掉也没关系,它也没起到相应的作用。

最后用代码总结一下这三种颜色之间的关系:

procedure TForm1.Button2Click(Sender: TObject);
begin
Brush.Color := clGreen;
if (Color=clGreen) then
ShowMessage('yes'); // 不执行
if (Canvas.Brush.Color=clGreen) then
ShowMessage('yes'); // 不执行
end; procedure TForm1.Button3Click(Sender: TObject);
begin
Color := clGreen;
if (Brush.Color=clGreen) then
ShowMessage('yes'); // 执行
if (Canvas.Brush.Color=clGreen) then
ShowMessage('yes'); // 执行
end; procedure TForm1.Button4Click(Sender: TObject);
begin
Canvas.Brush.Color := clGreen;
if (Brush.Color=clGreen) then
ShowMessage('yes'); // 不执行
if (Color=clGreen) then
ShowMessage('yes'); // 不执行
end;

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

另外还有一个问题是,这个颜色到底使用哪个winapi起作用的,通过搜索FillRect得知,是它在起作用

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); // 这里,重新填充背景色(相当于擦除旧的背景色),注意画刷都有句柄
end; Message.Result := 1;
end;

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

总结:改变类属性后就会立刻生效,原因是会调用类属性对应的Set函数,然后发消息真正显示到窗口上。如果WM_消息能直接解决问题(比如设置窗体标题),就行了,但有时候还不够,还需要使用CM_消息进一步帮助处理(比如这个例子改变窗体颜色)。

一行代码设置TForm颜色的前世今生(属性赋值引起函数调用,然后发消息实现改变显示效果),TForm的初始颜色在dfm中设置了clBtnFace色的更多相关文章

  1. C# 求精简用一行代码完成的多项判断 重复赋值

    C# 求精简用一行代码完成的多项判断 重复赋值 哈哈,说实话,个人看着这么长的三元操作也麻烦,但是我也只想到了这样三元判断句中执行方法体能够写到一行,追求的终极目的是,用一行实现这个过程,而且简单,由 ...

  2. thinkphp3.2.3中设置路由,优化url

    需求: 访问这个目录的时候,http://xx.com/p-412313要重定向到(暂且这么叫)http://xx.com/Home/Blog/index/id/412313 就是看着好看 我的应用目 ...

  3. spring-知识小结之注解为属性赋值

    <1>.本类中的属性赋值 public class UserServiceImpl implements UserService { //按照类别赋值 // @Autowired //按照 ...

  4. 在 Excel 中设置图片

    package com.smbea.demo.excel; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStr ...

  5. 一行代码设置TLabel.Caption的前世今生

    第零步,测试代码: procedure TForm1.Button1Click(Sender: TObject); begin Label1.Caption := 'Hello World'; end ...

  6. Android代码中设置字体大小,字体颜色,显示两种颜色.倒计时效果

    Android代码中设置字体大小,字体颜色,显示两种颜色 在xml文件中字体大小用的像素 <TextView android:id="@+id/uppaid_time" an ...

  7. 一行代码轻松修改 Text Field 和 Text View 的光标颜色 — By 昉

    众所周知,Text Field 和 Text View 的光标颜色默认都是系统应用的那种蓝色,如图: 而在实际开发中为了让视觉效果更统一,我们可能会想把那光标的颜色设置成和界面色调一致的颜色.其实在 ...

  8. [BS-03] 统一设置UITabBarController管理的所有VC的tabBarItem图标文字的颜色大小等属性

    1. 统一设置UITabBarController管理的所有VC的tabBarItem图标文字的颜色大小等属性 . 统一设置UITabBarController管理的所有VC的tabBarItem图标 ...

  9. 使用StoryBoard设置Scrollview的横向滚动不用一行代码

    1).创建一个空工程Single类型的工程,然后打开故事版(StoryBoard)在ViewController上添加scrollview 2).然后对scrollview添加约束,上下左右全部都是0 ...

随机推荐

  1. IOS- 网络图片缓存到沙盒中 ,离线取出。

    一.缓存图片 //1.首先创建在沙盒中创建一个文件夹用于保存图片 NSFileManager *fileManager = [[NSFileManager alloc] init]; NSString ...

  2. 字符串流sstream[part1/基本知识]

    C++中的输入输出分为三种:基于控制台的I/O,即istream.ostream.iostream:基于文件的I/O,即ifstream.ofstream.fstream:基于字符串的I/O,即ist ...

  3. Bootstrap入门四:代码

    1.内联代码 code: 通过 <code> 标签包裹内联样式的代码片段.灰色背景.灰色边框和红色字体. For example, <code><section>& ...

  4. window窗口-button(按钮)-dialog(对话框,带按钮)

    描述:一个可拖动的窗口程序,默认情况下窗口自由移动.调整大小.打开关闭! 案例1(普通的窗口): <div class="easyui-window" icon-Cls=&q ...

  5. BZOJ 4443: [Scoi2015]小凸玩矩阵 二分图最大匹配+二分

    题目链接: http://www.lydsy.com/JudgeOnline/problem.php?id=4443 题解: 二分答案,判断最大匹配是否>=n-k+1: #include< ...

  6. css中的border还可以这样玩

    在看这篇文章之前你可能会觉得border只是简单的绘制边框,看了这篇文章,我相信你也会跟我一样说一句"我靠,原来css中的border还可以这样玩".这篇文章主要是很早以前看了别人 ...

  7. 【HDOJ】【3480】Division

    DP/四边形不等式 要求将一个可重集S分成M个子集,求子集的极差的平方和最小是多少…… 首先我们先将这N个数排序,容易想到每个自己都对应着这个有序数组中的一段……而不会是互相穿插着= =因为交换一下明 ...

  8. 【BZOJ】【3238】【AHOI2013】diff(差异)

    题目链接:www.lydsy.com/JudgeOnline/problem.php?id=3238 后缀数组 这题题面给的暗示性就很强啊……一看就是要用后缀xx一家的算法,由于本蒻只会后缀数组所以就 ...

  9. discuzx完全自定义设计模板门户首页,栏目,专题模板方法

    第一种:门户首页模板(index.htm,保存于templatedefaultportal) <!--{subtemplate common/header}--> <style id ...

  10. Portlet之讲解

    Portlet在Web门户上管理和显示的可插拔的用户界面组件.Portlet产生可以聚合到门户页面中的标记语言代码的片段,如HTML,XML等.通常,根据桌面隐喻,一个门户页面显示为一组互相不重叠的p ...