一行代码设置TForm颜色的前世今生(属性赋值引起函数调用,然后发消息实现改变显示效果),TForm的初始颜色在dfm中设置了clBtnFace色
来自万一的帖子:
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色的更多相关文章
- C# 求精简用一行代码完成的多项判断 重复赋值
C# 求精简用一行代码完成的多项判断 重复赋值 哈哈,说实话,个人看着这么长的三元操作也麻烦,但是我也只想到了这样三元判断句中执行方法体能够写到一行,追求的终极目的是,用一行实现这个过程,而且简单,由 ...
- thinkphp3.2.3中设置路由,优化url
需求: 访问这个目录的时候,http://xx.com/p-412313要重定向到(暂且这么叫)http://xx.com/Home/Blog/index/id/412313 就是看着好看 我的应用目 ...
- spring-知识小结之注解为属性赋值
<1>.本类中的属性赋值 public class UserServiceImpl implements UserService { //按照类别赋值 // @Autowired //按照 ...
- 在 Excel 中设置图片
package com.smbea.demo.excel; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStr ...
- 一行代码设置TLabel.Caption的前世今生
第零步,测试代码: procedure TForm1.Button1Click(Sender: TObject); begin Label1.Caption := 'Hello World'; end ...
- Android代码中设置字体大小,字体颜色,显示两种颜色.倒计时效果
Android代码中设置字体大小,字体颜色,显示两种颜色 在xml文件中字体大小用的像素 <TextView android:id="@+id/uppaid_time" an ...
- 一行代码轻松修改 Text Field 和 Text View 的光标颜色 — By 昉
众所周知,Text Field 和 Text View 的光标颜色默认都是系统应用的那种蓝色,如图: 而在实际开发中为了让视觉效果更统一,我们可能会想把那光标的颜色设置成和界面色调一致的颜色.其实在 ...
- [BS-03] 统一设置UITabBarController管理的所有VC的tabBarItem图标文字的颜色大小等属性
1. 统一设置UITabBarController管理的所有VC的tabBarItem图标文字的颜色大小等属性 . 统一设置UITabBarController管理的所有VC的tabBarItem图标 ...
- 使用StoryBoard设置Scrollview的横向滚动不用一行代码
1).创建一个空工程Single类型的工程,然后打开故事版(StoryBoard)在ViewController上添加scrollview 2).然后对scrollview添加约束,上下左右全部都是0 ...
随机推荐
- UserDefault 用户首选项读写 swift
// // ViewController.swift // 首选项数据读写 // // Created by mac on 15/7/12. // Copyright (c) 2015年 fangyu ...
- 内部类&匿名内部类
内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象.这时,为了方便设计和访问,直接将A类定义在B类中.就可以了.A类就称为内部类.内部类可以直接访问外部类中的成员.而外部类想要访问内 ...
- Midway-ModelProxy — 轻量级的接口配置建模框架
Midway-ModelProxy - 轻量级的接口配置建模框架 前言 使用Node做前后端分离的开发模式带来了一些性能及开发流程上的优势(见<前后端分离的思考与实践 一>), 但同时也面 ...
- Java_Swing实现小球沿正弦曲线运动的代码
1 package zuidaimapack_1; import java.awt.*; import javax.swing.*; /** *Java_Swing实现小球沿正弦曲线运动的代码 * @ ...
- python 实现斐波那契数列
def fib(n): a,b=0,1 while a<n: print(a,end=" ") a,b=b,a+b print() fib(2000) 输出: 0 1 1 2 ...
- js判断手机还是pc并跳转相关页面
<script type="text/javascript"> function GetRequest() { var url = location.search; / ...
- JVM 崩溃 Failed to write core dump解决办法 WINDOWS
JVM 崩溃 Failed to write core dump解决办法 WINDOWS MIT key words: JVM,崩溃,windows,Failed,core dump,虚拟内存 最近从 ...
- bzoj 1189 二分+最大流判定
首先我们可以二分一个答案时间T,这样就将最优性问题 转化为了判定性问题.下面我们考虑对于已知的T的判定 对于矩阵中所有的空点bfs一次,得出来每个点到门的距离, 然后连接空点和每个能在t时间内到达的门 ...
- 【BZOJ】【4003】【JLOI2015】城池攻占
可并堆 QAQ改了一下午……最终弃疗求助zyf……居然被秒了QAQ真是弱到不行(zyf太神了Orz) 还是先考虑部分分的做法: 1.$n,m\leq 3000$:可以暴力模拟每个骑士的攻打过程,也可以 ...
- if-else的优化举例
共有部分: String bookFrom = null; String sheetFrom = null; String bookTo = null; String sheetTo = null; ...