我忽然发现:TButton既没有处理WM_PAINT,又没有Paint()或者PaintWindow(),那么它是什么时候被绘制的?

Form1上放2个TButton,然后设置代码:

  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3. button2.Repaint;
  4. end;
  5.  
  6. procedure TForm1.Button2Click(Sender: TObject);
  7. begin
  8. ShowMessage('good');
  9. end;

在Form1第一次显示时,应该会让这两个Button显示。这两个Button应该会处理WM_PAINT并显示。可是完全找不到相关代码啊。

后来用脑子想了想,TButton是TWinControl,而且是没有图形子控件的,所以它收到WM_PAINT时应该会依次执行:

  1. procedure TButtonControl.WndProc(var Message: TMessage);
  2. begin
  3. case Message.Msg of
  4. WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
  5. if not (csDesigning in ComponentState) and not Focused then
  6. begin
  7. FClicksDisabled := True;
  8. Windows.SetFocus(Handle);
  9. FClicksDisabled := False;
  10. if not Focused then Exit;
  11. end;
  12. CN_COMMAND:
  13. if FClicksDisabled then Exit;
  14. end;
  15. inherited WndProc(Message);
  16. end;
  17.  
  18. procedure TWinControl.WndProc(var Message: TMessage);
  19. var
  20. Form: TCustomForm;
  21. begin
  22. case Message.Msg of
  23. WM_SETFOCUS:
  24. begin
  25. Form := GetParentForm(Self);
  26. if (Form <> nil) and not Form.SetFocusedControl(Self) then Exit;
  27. end;
  28. WM_KILLFOCUS:
  29. if csFocusing in ControlState then Exit;
  30. WM_NCHITTEST:
  31. begin
  32. inherited WndProc(Message);
  33. if (Message.Result = HTTRANSPARENT) and (ControlAtPos(ScreenToClient(
  34. SmallPointToPoint(TWMNCHitTest(Message).Pos)), False) <> nil) then
  35. Message.Result := HTCLIENT;
  36. Exit;
  37. end;
  38. WM_MOUSEFIRST..WM_MOUSELAST:
  39. if IsControlMouseMsg(TWMMouse(Message)) then
  40. begin
  41. { Check HandleAllocated because IsControlMouseMsg might have freed the
  42. window if user code executed something like Parent := nil. }
  43. if (Message.Result = ) and HandleAllocated then
  44. DefWindowProc(Handle, Message.Msg, Message.wParam, Message.lParam);
  45. Exit;
  46. end;
  47. WM_KEYFIRST..WM_KEYLAST:
  48. if Dragging then Exit;
  49. WM_CANCELMODE:
  50. if (GetCapture = Handle) and (CaptureControl <> nil) and
  51. (CaptureControl.Parent = Self) then
  52. CaptureControl.Perform(WM_CANCELMODE, , );
  53. end;
  54. inherited WndProc(Message);
  55. end;
  56.  
  57. procedure TControl.WndProc(var Message: TMessage);
  58. var
  59. Form: TCustomForm;
  60. KeyState: TKeyboardState;
  61. WheelMsg: TCMMouseWheel;
  62. begin
  63. if (csDesigning in ComponentState) then
  64. begin
  65. Form := GetParentForm(Self);
  66. if (Form <> nil) and (Form.Designer <> nil) and
  67. Form.Designer.IsDesignMsg(Self, Message) then Exit
  68. end;
  69. if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then
  70. begin
  71. Form := GetParentForm(Self);
  72. if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;
  73. end
  74. else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
  75. begin
  76. if not (csDoubleClicks in ControlStyle) then
  77. case Message.Msg of
  78. WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
  79. Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
  80. end;
  81. case Message.Msg of
  82. WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);
  83. WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
  84. begin
  85. if FDragMode = dmAutomatic then
  86. begin
  87. BeginAutoDrag;
  88. Exit;
  89. end;
  90. Include(FControlState, csLButtonDown);
  91. end;
  92. WM_LBUTTONUP:
  93. Exclude(FControlState, csLButtonDown);
  94. else
  95. with Mouse do
  96. if WheelPresent and (RegWheelMessage <> ) and
  97. (Message.Msg = RegWheelMessage) then
  98. begin
  99. GetKeyboardState(KeyState);
  100. with WheelMsg do
  101. begin
  102. Msg := Message.Msg;
  103. ShiftState := KeyboardStateToShiftState(KeyState);
  104. WheelDelta := Message.WParam;
  105. Pos := TSmallPoint(Message.LParam);
  106. end;
  107. MouseWheelHandler(TMessage(WheelMsg));
  108. Exit;
  109. end;
  110. end;
  111. end
  112. else if Message.Msg = CM_VISIBLECHANGED then
  113. with Message do
  114. SendDockNotification(Msg, WParam, LParam);
  115. Dispatch(Message);
  116. end;
  117.  
  118. procedure TWinControl.WMPaint(var Message: TWMPaint);
  119. var
  120. DC, MemDC: HDC;
  121. MemBitmap, OldBitmap: HBITMAP;
  122. PS: TPaintStruct;
  123. begin
  124. if not FDoubleBuffered or (Message.DC <> ) then
  125. begin
  126. if not (csCustomPaint in ControlState) and (ControlCount = ) then
  127. inherited // 执行这里,会执行TWinControl.DefaultHandler,因为TButton和TButtonControl都没有覆盖DefaultHandler函数
  128. else
  129. PaintHandler(Message);
  130. end
  131. else
  132. begin
  133. DC := GetDC();
  134. MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom);
  135. ReleaseDC(, DC);
  136. MemDC := CreateCompatibleDC();
  137. OldBitmap := SelectObject(MemDC, MemBitmap);
  138. try
  139. DC := BeginPaint(Handle, PS);
  140. Perform(WM_ERASEBKGND, MemDC, MemDC);
  141. Message.DC := MemDC;
  142. WMPaint(Message);
  143. Message.DC := ;
  144. BitBlt(DC, , , ClientRect.Right, ClientRect.Bottom, MemDC, , , SRCCOPY);
  145. EndPaint(Handle, PS);
  146. finally
  147. SelectObject(MemDC, OldBitmap);
  148. DeleteDC(MemDC);
  149. DeleteObject(MemBitmap);
  150. end;
  151. end;
  152. end;

所以就真的没办法,只能执行:

  1. procedure TWinControl.DefaultHandler(var Message);
  2. begin
  3. if FHandle <> then
  4. begin
  5. with TMessage(Message) do
  6. begin
  7. if (Msg = WM_CONTEXTMENU) and (Parent <> nil) then
  8. begin
  9. Result := Parent.Perform(Msg, WParam, LParam);
  10. if Result <> then Exit;
  11. end;
  12. case Msg of
  13. WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
  14. Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam);
  15. CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
  16. begin
  17. SetTextColor(WParam, ColorToRGB(FFont.Color));
  18. SetBkColor(WParam, ColorToRGB(FBrush.Color));
  19. Result := FBrush.Handle;
  20. end;
  21. else
  22. if Msg = RM_GetObjectInstance then
  23. Result := Integer(Self)
  24. else
  25. begin
  26. if Msg <> WM_PAINT then // 稍微改造一下,否则程序执行会出错
  27. Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam); // 会执行到这里!
  28. end;
  29. end;
  30. if Msg = WM_SETTEXT then
  31. SendDockNotification(Msg, WParam, LParam);
  32. end;
  33. end
  34. else
  35. inherited DefaultHandler(Message);
  36. end;

执行完TWinControl.DefaultHandler后,程序对TButton的WM_PAINT消息就算处理完毕了,会一路返回。

百思不得其解的情况下,忽然灵机一动,TButton是封装了微软的Button,虽然其代码不可见,但也应该是正确处理了WM_PAINT消息的,那么只要把WM_PAINT消息发给这个Button的句柄,不就可以正确显示了?这一切,还多亏了CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam);的调用。我想完全屏蔽这句试试效果,结果程序运行出错,因为这样一来许多消息都无法得到处理。那么就加个条件吧:if Msg <> WM_PAINT then 结果发现,两个Button果然灰蒙蒙一片,无法正确显示!但是点击执行事件还是没问题的,而且点击之后,就可以正确显示了,经过研究,又有一番滋味:

  1. procedure TButtonControl.WndProc(var Message: TMessage);
  2. begin
  3. case Message.Msg of
  4. WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
  5. if not (csDesigning in ComponentState) and not Focused then
  6. begin
  7. FClicksDisabled := True;
  8. Windows.SetFocus(Handle);
  9. FClicksDisabled := False;
  10. if not Focused then Exit;
  11. end;
  12. CN_COMMAND:
  13. if FClicksDisabled then Exit;
  14. end;
  15. inherited WndProc(Message);
  16. end;
  17.  
  18. procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);
  19. begin
  20. SendCancelMode(Self);
  21. inherited; // 这里,也会调用TWinControl.DefaultHandler,而且传递的消息是WM_LBUTTONDOWN
  22. if csCaptureMouse in ControlStyle then MouseCapture := True;
  23. if csClickEvents in ControlStyle then Include(FControlState, csClicked);
  24. DoMouseDown(Message, mbLeft, []);
  25. end;
  26.  
  27. procedure TWinControl.DefaultHandler(var Message);
  28. begin
  29. if FHandle <> then
  30. begin
  31. with TMessage(Message) do
  32. begin
  33. if (Msg = WM_CONTEXTMENU) and (Parent <> nil) then
  34. begin
  35. Result := Parent.Perform(Msg, WParam, LParam);
  36. if Result <> then Exit;
  37. end;
  38. case Msg of
  39. WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
  40. Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam);
  41. CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
  42. begin
  43. SetTextColor(WParam, ColorToRGB(FFont.Color));
  44. SetBkColor(WParam, ColorToRGB(FBrush.Color));
  45. Result := FBrush.Handle;
  46. end;
  47. else
  48. if Msg = RM_GetObjectInstance then
  49. Result := Integer(Self)
  50. else
  51. begin
  52. if Msg <> WM_PAINT then // 因为是WM_LBUTTONDOWN,所以照样会传进去
  53. Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam);
  54. end;
  55. end;
  56. if Msg = WM_SETTEXT then
  57. SendDockNotification(Msg, WParam, LParam);
  58. end;
  59. end
  60. else
  61. inherited DefaultHandler(Message);
  62. end;

之所以点击以后,会重新出现完整的Button,是因为点击以后,Button的显示情况要变,趁此机会,微软的Button就把它重绘了一遍。真是好复杂呀,好多都是靠猜的。

经测试,TEdit也是同样的效果(没WM_PAINT函数,没Paint,屏蔽WM_PAINT消息后就无法正确显示)。

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

理论回答:

应该是调用的Win32标准控件库,绘制代码应该在comctl32.dll中
在TWinControl.CreateWnd中调用CreateParams方法指定基本的外观风格
所以你看TButton有重写CreateParams方法
绘制是交给系统来做的,如果想更改,就要自绘

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

新问题:情况1:空窗体放Panel1,不影响

情况2:空窗体放Panel1和Button1,这时候Panel1就受影响了。什么叫做影响?就是Panel1无法得到正确的绘制。

目前不知道为什么。

有那么一点点可能,是拦截了Form1的WM_PAINT,它作为Parent就没有发送消息给子控件。不过TWinControl都是独立的,应该不需要父控件来管理发送呀。

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

这个,你看一下Panel和Button的CreateParams下的代码
它们的Style有区别,不知道是不是这里面的原因

另外,你试试将Windows的主题设置“经典”样式

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

为什么WM_PAINT不送到BUTTON手里,Windows会誓不罢休?
任何一个控制,如果没有调用 BeginPaint/EndPaint,Windows都会不罢休

它拦截了 DefaultHandler 里的 WM_PAINT,但没有处理,Windows 就会不停的重发 WM_PAINT 给它,造成后陆的消息无法得到及时处理
如果它拦截了,并调用了BeginPaint/EndPaint 处理了,就不会有今晚的问题了

1. 我明明Panel写在前面,为什么会先发送消息到Button里?
2. 我拖动窗口的时候,为什么没有消息堵塞的问题?

Z-Order 这个东西,可以说一个简单的公理,它的定义本身就决定了没有所谓的并行
Z-Order 是所谓的 Z 轴的顺序,你就想像一下,你排一个队,两个怎么也就有先后

还在纠结
我不是说了么
拖动的时候,直接发送到控件去了
又一个调度
第一个整体显示的消息处理调度还在死循环中
,你这个排列,那么久说明会先绘制Button,然后绘制Panel,是按照ZOrder绘制的,先绘制前面的
你把Panel右键设置BringToFront,于是就直接显示了
,说的很清楚了啊

终于懂了:TWinControl.DefaultHandler里的CallWindowProc(FDefWndProc)还挺有深意的,TButton对WM_PAINT消息的处理就是靠它来处理的(以前不明白为什么总是要调用inherited,其实就是没有明白TWinControl.DefaultHandler的真正用处)的更多相关文章

  1. TWinControl、TCustomControl和TGraphicControl对WM_PAINT消息的三种不同处理(虚函数的特点就是升升降降)

    -------------------- TWinControl收到WM_Paint消息(以后找个例子)-------------------- 1. 消息函数 TWinControl.WMPaint ...

  2. 终于懂了:TControl.Perform是有返回值的,且看VCL框架如何利用消息的返回值(全部例子都在这里)——它的存在仅仅是为了方便复用消息的返回值

    代码如下: function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint; var Message: TMess ...

  3. 终于懂了:WM_PAINT 与 WM_ERASEBKGND(三种情况:用户操作,UpdateWindow,InvalidateRect产生的效果并不相同),并且用Delphi代码验证 good

    一直对这两个消息的关系不是太了解,借重新深刻学习windows编程的机会研究一番. 1)当窗口从无效变为有效时,比方将部分覆盖的窗口恢复时会重绘窗口时:程序首先会通过发送其他消息调用DefWindow ...

  4. 终于懂了:FWinControls子控件的显示是由Windows来管理,而不是由Delphi来管理(显示透明会导致计算无效区域的方式有所不同——透明的话应减少剪裁区域,所以要进行仔细计算)

    在研究TCustomControl的显示过程中,怎么样都找不到刷新FWinControls并重新显示的代码: procedure TWinControl.PaintHandler(var Messag ...

  5. 终于懂了:Delphi消息的Result域出现的原因——要代替回调函数的返回值!(MakeObjectInstance不会帮助处理(接收)消息回调函数的返回值)

    MakeObjectInstance应该不会帮助处理(接收)消息回调函数的返回值,可是有时候又确实需要这个返回值,这可怎么办呢?我是看到这段文字的时候,想到这个问题的: 当WM_PAINT不是由Inv ...

  6. 把消息送到默认窗口函数里,并非一点用都没有,可能会产生新的消息(以WM_WINDOWPOSCHANGED为例)

    我在追踪执行: procedure TForm1.Button1Click(Sender: TObject); begin panel1.Left:=panel1.Left-; end; 并且屏蔽TW ...

  7. VC里OnPaint几点要注意的地方(没有invalidate,系统认为窗口没有更新的必要,于是就对发来的WM_PAINT消息不理不睬)

    写在属于自己的体会,哪怕只是一点点,也是真的懂了.否则有那么多书,如果只是不过脑子的学一遍看一遍,又有谁真的掌握了这些知识呢? 这样你或许就明白了为什么不能直接用SendMessage和PostMes ...

  8. 终于懂了:TWinControl主要是Delphi官方用来封装Windows的官方控件,开发者还是应该是有TCustomControl来开发三方控件

    再具体一点,就是TWinControl一般情况下不需要Canvas和Paint(TForm是个例外),而TCustomControl自带这2个. 同时开发者应该使用TGraphicControl,而不 ...

  9. 终于懂了:WM_PAINT中应该用BeginPaint与EndPaint这两个api,它们的功能正是使无效区域恢复(所以WM_PAINT里即使什么都不做,也必须写上BeginPaint与EndPaint)——Delphi里WM_PAINT消息的三个走向都做到了这一点 good

    程序本来是想实现鼠标单击改变背景颜色.可是,程序运行时,为什么没有任何消息触发,背景颜色就一直不断的改变了?WM_PAINT怎么被触发的 #include <windows.h> #inc ...

随机推荐

  1. 为什么要选择cdn加速

    CDN的通俗理解就是网站加速,CPU均衡负载,可以解决跨运营商,跨地区,服务器负载能力过低,带宽过少等带来的网站打开速度慢等问题. 比如: 1.一个企业的网站服务器在北京,运营商是电信,在广东的联通用 ...

  2. Cannot drop the database ‘XXX’ because it is being used for replication.

    删除订阅数据库的时候出现下面的错误: Cannot drop the database ‘XXX’  because it is being used for replication. 数据库的状态为 ...

  3. Python获取当地的天气和随意城市的天气

    先从中国天气网得到数据('http://www.weather.com.cn/data/cityinfo/'+城市编码),每一个城市都有各自的编码,怎样得到用户所在地的城市编码呢?用一个网页就是专门干 ...

  4. XML(三)

     使用 XSLT 显示 XML -------------------------------------------------------------------------------- 通 ...

  5. stm32之ADC

    将模拟量转换为数字量的过程称为模式(A/D)转换,完成这一转换的期间成为模数转换器(简称ADC);将数字量转换为模拟量的过程为数模(D/A)转换,完成这一转换的器件称为数模转换器(简称DAC). 模拟 ...

  6. Android下调用收发短信邮件等

    Android下调用收发短信邮件等 1,调web浏览器Uri myBlogUri = Uri.parse("http://xxxxx.com");returnIt = new In ...

  7. Orleans is a framework

    Introduction Orleans is a framework that provides a straightforward approach to building distributed ...

  8. [HDU 1358]Period[kmp求周期]

    题意: 每一个power前缀的周期数(>1). 思路: kmp的next. 每一个前缀都询问一遍. #include <cstring> #include <cstdio> ...

  9. crm高速开发之QueryExpression

    /* 创建者:菜刀居士的博客  * 创建日期:2014年07月06号  */ namespace Net.CRM.OrganizationService {     using System;     ...

  10. android Gallery滑动不流畅的解决

    import android.content.Context; import android.util.AttributeSet; import android.view.KeyEvent; impo ...