TWinControl的刷新过程(一共5个函数,默认都不是双缓冲,注意区分是TCustomControl还是Windows原生封装控件,执行流程不一样)

前提条件:要明白在TWinControl有以下四个函数的存在,注意都是虚函数:

procedure Invalidate; override;
procedure Update; override;
procedure Repaint; override; // 相当于前两句的组合
procedure CMInvalidate(var Message: TMessage); message CM_INVALIDATE;

还有从TControl继承来的函数:    procedure Refresh;

procedure TControl.Refresh;
begin
  Repaint;
end;
procedure TWinControl.Invalidate;
begin
  Perform(CM_INVALIDATE, 0, 0);
end;

procedure TWinControl.Update;
begin
  if HandleAllocated then UpdateWindow(FHandle);
end;

procedure TWinControl.Repaint;
begin
  Invalidate;
  Update;
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);
    if Message.WParam = 0 then
    begin
      InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle));
    end;
  end;
end;

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

举例1:按钮刷新

procedure TForm1.Button2Click(Sender: TObject);
begin
  Button1.Invalidate;
  Button1.Update;
end;

执行过程:

procedure TWinControl.Invalidate;
begin
  Perform(CM_INVALIDATE, 0, 0);
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);
    if Message.WParam = 0 then
    begin
      InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle));
  end;
end;

procedure TWinControl.Update;
begin
  if HandleAllocated then UpdateWindow(FHandle); // 产生WM_PAINT消息
end;
procedure TWinControl.WMPaint(var Message: TWMPaint);
procedure TWinControl.DefaultHandler(var Message);

其中WMPaint函数里有判断:

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

因为TButton本质上是包装了Button,所以最后的结果是在TWinControl.DefaultHandler里执行了:

Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam);

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

举例2:Panel刷新

procedure TForm1.Button2Click(Sender: TObject);
begin
  Panel1.Invalidate;
  Panel1.Update;
end;

区别在于,Panel1有句柄,失效后,可自己接受WM_Paint进行刷新,其执行过程如下:

procedure TWinControl.Update;
begin
  if HandleAllocated then UpdateWindow(FHandle); // 产生WM_PAINT消息
end;

// WM_PAINT消息会发送到Panel1的MainWndProc函数(MakeObjectInstance转换后存储的地址)
procedure TWinControl.MainWndProc(var Message: TMessage);
begin
      WindowProc(Message);
end;

procedure TWinControl.WndProc(var Message: TMessage);
begin
  inherited WndProc(Message);
end;

procedure TControl.WndProc(var Message: TMessage);
begin
  Dispatch(Message);
end;

// Dispath后,终于在消息函数里找到响应函数
procedure TCustomControl.WMPaint(var Message: TWMPaint);
begin
  Include(FControlState, csCustomPaint); // 注意,只有继承自TCustomControl的控件,才有这个标志位。另外TForm也有。
  inherited;
  Exclude(FControlState, csCustomPaint);
end;

procedure TWinControl.WMPaint(var Message: TWMPaint);
var
  DC, MemDC: HDC;
  MemBitmap, OldBitmap: HBITMAP;
  PS: TPaintStruct;
begin
  if not FDoubleBuffered or (Message.DC <> 0) then
  begin
    if not (csCustomPaint in ControlState) and (ControlCount = 0) then
      inherited // 对于没有子控件的系统包装控件执行这里,分得清清楚楚
    else
      PaintHandler(Message); // 执行这里
  end
end;

procedure TWinControl.PaintHandler(var Message: TWMPaint);
var
  I, Clip, SaveIndex: Integer;
  DC: HDC;
  PS: TPaintStruct;
begin
  DC := Message.DC;
  if DC = 0 then DC := BeginPaint(Handle, PS);
  try
    if FControls = nil then PaintWindow(DC) else
    begin
      SaveIndex := SaveDC(DC);
      Clip := SimpleRegion;
      for I := 0 to FControls.Count - 1 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 = 0 then EndPaint(Handle, PS);
  end;
end;

控件终于可以自绘自己了:

procedure TCustomControl.PaintWindow(DC: HDC);
begin
  FCanvas.Lock;
  try
    FCanvas.Handle := DC;
    try
      TControlCanvas(FCanvas).UpdateTextFlags;
      Paint;
    finally
      FCanvas.Handle := 0;
    end;
  finally
    FCanvas.Unlock;
  end;
end;

// 现场画出来。注意,TPanel没有OnPaint事件,所以就是控件纯自绘,程序员没机会插手
procedure TCustomPanel.Paint;
const
  Alignments: array[TAlignment] of Longint = (DT_LEFT, DT_RIGHT, DT_CENTER);
var
  Rect: TRect;
  TopColor, BottomColor: TColor;
  FontHeight: Integer;
  Flags: Longint;

  procedure AdjustColors(Bevel: TPanelBevel);
  begin
    TopColor := clBtnHighlight;
    if Bevel = bvLowered then TopColor := clBtnShadow;
    BottomColor := clBtnShadow;
    if Bevel = bvLowered then BottomColor := clBtnHighlight;
  end;

begin
  Rect := GetClientRect;
  if BevelOuter <> bvNone then
  begin
    AdjustColors(BevelOuter);
    Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
  end;
  Frame3D(Canvas, Rect, Color, Color, BorderWidth);
  if BevelInner <> bvNone then
  begin
    AdjustColors(BevelInner);
    Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
  end;
  with Canvas do
  begin
    if not ThemeServices.ThemesEnabled or not ParentBackground then
    begin
      Brush.Color := Color;
      FillRect(Rect);
    end;
    Brush.Style := bsClear;
    Font := Self.Font;
    FontHeight := TextHeight(‘W‘);
    with Rect do
    begin
      Top := ((Bottom + Top) - FontHeight) div 2;
      Bottom := Top + FontHeight;
    end;
    Flags := DT_EXPANDTABS or DT_VCENTER or Alignments[FAlignment];
    Flags := DrawTextBiDiModeFlags(Flags);
    DrawText(Handle, PChar(Caption), -1, Rect, Flags);
  end;
end;

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

举例3:Form刷新

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1.Invalidate;
  Form1.Update;
end;

执行:

procedure TWinControl.Invalidate;
begin
  Perform(CM_INVALIDATE, 0, 0);
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);
    if Message.WParam = 0 then
    begin
      InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle));
    end;
  end;
end;

procedure TWinControl.Update;
begin
  if HandleAllocated then UpdateWindow(FHandle); // 产生WM_PAINT消息
end;

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

procedure TWinControl.WMPaint(var Message: TWMPaint);
var
  DC, MemDC: HDC;
  MemBitmap, OldBitmap: HBITMAP;
  PS: TPaintStruct;
begin
  if not FDoubleBuffered or (Message.DC <> 0) then
  begin
    if not (csCustomPaint in ControlState) and (ControlCount = 0) then
      inherited
    else
      PaintHandler(Message); // 执行这里
  end
  else
  begin
    DC := GetDC(0);
    MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom);
    ReleaseDC(0, DC);
    MemDC := CreateCompatibleDC(0);
    OldBitmap := SelectObject(MemDC, MemBitmap);
    try
      DC := BeginPaint(Handle, PS);
      Perform(WM_ERASEBKGND, MemDC, MemDC);
      Message.DC := MemDC;
      WMPaint(Message);
      Message.DC := 0;
      BitBlt(DC, 0, 0, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, 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 = 0 then DC := BeginPaint(Handle, PS);
  try
    if FControls = nil then PaintWindow(DC) else
    begin
      SaveIndex := SaveDC(DC);
      Clip := SimpleRegion;
      for I := 0 to FControls.Count - 1 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 = 0 then EndPaint(Handle, PS);
  end;
end;
// TWinControl.PaintHandler 包括执行:
procedure TCustomForm.PaintWindow(DC: HDC); // 绘制自己
procedure TCustomForm.Paint; // 调用程序员事件
procedure TWinControl.PaintControls(DC: HDC; First: TControl); // 注意,此函数只重绘图形子控件

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

举例4:Win控件开启DoubleBuffer的功能

注意,DoubleBuffered是TWinControl的属性

procedure TForm1.Button1Click(Sender: TObject);
begin
  Panel1.DoubleBuffered := true;
  Panel1.Invalidate;
  Panel1.Update;
end;

执行过程:

procedure TWinControl.WMPaint(var Message: TWMPaint);
var
  DC, MemDC: HDC;
  MemBitmap, OldBitmap: HBITMAP;
  PS: TPaintStruct;
begin
  if not FDoubleBuffered or (Message.DC <> 0) then
  begin
    if not (csCustomPaint in ControlState) and (ControlCount = 0) then
      inherited
    else
      PaintHandler(Message);
  end
  else // 第一次执行会走这里!
  begin
    DC := GetDC(0);
    MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom);
    ReleaseDC(0, DC);
    MemDC := CreateCompatibleDC(0);
    OldBitmap := SelectObject(MemDC, MemBitmap);
    try
      DC := BeginPaint(Handle, PS);
      Perform(WM_ERASEBKGND, MemDC, MemDC);
      Message.DC := MemDC; // 使用内存DC,这样下次递归判断条件的时候,就会把控件都绘制在内存DC上,最后靠BitBlt把它们一次性绘制在当前控件Handle的DC上,好像也不难理解
      WMPaint(Message); // 递归执行
      Message.DC := 0;
      BitBlt(DC, 0, 0, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, SRCCOPY);
      EndPaint(Handle, PS);
    finally
      SelectObject(MemDC, OldBitmap);
      DeleteDC(MemDC);
      DeleteObject(MemBitmap);
    end;
  end;
end;

但是双缓冲对于Win控件的意义还不清楚,但是对它的图像子控件起作用?

时间: 2024-10-05 23:25:50

TWinControl的刷新过程(一共5个函数,默认都不是双缓冲,注意区分是TCustomControl还是Windows原生封装控件,执行流程不一样)的相关文章

VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程

开篇语:最近在弄ocx控件发布到asp.net网站上使用,就是用户在使用过程中,自动下载安装ocx控件.(此文章也是总结了网上好多人写的文章,我只是汇总一下,加上部分自己的东西,在这里感谢所有在网上发表相关内容的朋友们.) ActiveX控件用于Web的过程是将控件嵌入主页中,用户通过浏览器访问该主页时,将主页中的控件下载,并在用户机器上注册,以后就可在用户的浏览器上运行.控件下载一次后就驻留在用户本地机器上,下次再访问相同的主页时,可不再下载该控件,而是直接运行用户本地的控件.这里控件容器就是

AJAX中UPDATEPANEL配合TIMER控件实现局部无刷新

首先加入UpdatePanel <asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional"> //注:UpdateMode为更新模式,设置此属性表示只刷新UpdatePanel中的部分,若不加此属性,默认为Always,刷新整个页面. //OnTick为时间促发函数(后台函数),Interval为时间5000为5秒,即每5秒后促发此函数     

VS2010/MFC对话框:为控件添加消息处理函数

为控件添加消息处理函数 创建对话框类和添加控件变量在上一讲中已经讲过,这一讲的主要内容是如何为控件添加消息处理函数. MFC为对话框和控件等定义了诸多消息,我们对它们操作时会触发消息,这些消息最终由消息处理函数处理.比如我们点击按钮时就会产生BN_CLICKED消息,修改编辑框内容时会产生EN_CHANGE消息等.一般为了让某种操作达到效果,我们只需要实现某个消息的消息处理函数. 一.添加消息处理函数 以前面的加法计算器的程序为例,说明怎样为“计算”按钮控件添加消息处理函数.添加方法列出4种:

Effective C++ 条款九、十 绝不在构造和析构过程中调用virtual函数|令operator=返回一个reference to *this

  1.当在一个子类当中调用构造函数,其父类构造函数肯定先被调用.如果此时父类构造函数中有一个virtual函数,子类当中也有,肯定执行父类当中的virtual函数,而此时子类当中的成员变量并未被初始化,所以无法调用子类与之对应的函数.即为指向虚函数表的指针vptr没被初始化又怎么去调用派生类的virtual函数呢?析构函数也相同,派生类先于基类被析构,又如何去找派生类相应的虚函数? 2.做法:将子类的某个函数改为non-virtual,然后在子类构造函数中传递参数给父类函数.然后父类的构造函数

Effective C++ Item 9 绝不在构造和析构过程中调用virtual函数

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层) 示例: <pre name="code" class="cpp">#include <iostream> #include <string> using namespace std; c

下拉刷新-过程详解

Android的ListView是应用最广的一个组件,功能强大,扩展性灵活(不局限于ListView本身一个类),前面的文章有介绍分组,拖拽,3D立体,游标,圆角,而今天我们要介绍的是另外一个扩展ListView:下拉刷新的ListView.    下拉刷新界面最初流行于iphone应用界面,如图:     然后在Android中也逐渐被应用,比如微博,资讯类.    所以,今天要实现的结果应该也是类似的,先贴出最终完成效果,如下图,接下来我们一步一步实现. 1. 流程分析    下拉刷新最主要

Effective C++_笔记_条款09_绝不在构造和析构过程中调用virtual函数

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为方便采用书上的例子,先提出问题,在说解决方案. 1 问题 1: class Transaction{ 2: public: 3: Transaction(); 4: virtual void LogTransaction() const = 0 ; 5: ... 6: }; 7:  8: Transaction::Transaction() //Base cl

C++IO小程序附带运行过程 & get()函数解析

     int ch,count=0; ch=cin.get(); while(ch!=EOF) { cout.put(ch); ++count; ch=cin.get(); } cout<<count; cin.get(ch)与cin.get() 属性 cin.get(ch) ch=cin.get() 传递输入字符的方式 赋给参数ch 将函数返回值赋给ch 用于字符输入时函数的返回值 istream对象(执行布尔转换后为true) int类型的字符编码 到达EOF时函数的返回值 istre

[原创]Delphi FreeAndNil 是一个过程,并不是函数

Delphi FreeAndNil 是一个过程,并不是函数,看源代码就知道,它的主要作用是清空并释放对象 procedure FreeAndNil(var Obj); var Temp: TObject; begin Temp := TObject(Obj); Pointer(Obj) := nil; Temp.Free; end; 看代码的执行顺序,先置空,再释放 置空  清空指针指向内存的地址. 释放 释放实例占用的所有资源.Free后,指针不能再使用 更新时间:2019.12.27 来源于