1、Windows的窗口刷新管理
窗口句柄(HWND)都是由操作系统内核管理的,系统内部有一个z-order序列,记录着当前窗口从屏幕底部(假象的从屏幕到眼睛的方向),到屏幕最高层的一个窗口句柄的排序,这个排序不关注父窗口还是子窗口。
当任意一个窗口接收到WM_PAINT消息产生重绘,更新区域绘制完成以后,就搜索它的前面的一个窗口,如果此窗口的范围和更新区域有交集,就向这个窗口发送WM_PAINT消息,周而复始,直到执行到顶层窗口。才算完成。
1.1 父子窗口间的刷新管理对于一个对话框(主窗口)来说,理论上其所有子窗口都在他的前面——也就是更靠近眼睛的位置),当主窗口接收WM_PAINT绘制完成后,会引起更新区域上所有子窗口的重绘(所有子窗口也是自底向上排序的)。
子窗口是具有WS_CHILD或者WS_CHILDWINDOW样式的窗口。和一般窗口一样,子窗口通过WM_PAINT来绘图。子窗口也维护一个更新区域,应用程序和系统都可以通过设置该更新区域无效来产生WM_PAINT消息。
子窗口的更新和显示区域受到父窗口的影响,其他样式的窗口则不会。系统常常设置父窗口的更新区域的同时设置子窗口的更新区域,使父窗口收到WM_PAINT消息的同时子窗口也能收到WM_PAINT消息。系统把子窗口的位置限制在父窗口的client区域,超出这个区域就会被裁减掉。
无论何时,只要父窗口的更新区域包含了子窗口的一部分,系统就会为子窗口设置更新区域。此时,系统先向父窗口发送WM_PAINT消息,然后向子窗口发送消息让子窗口可以恢复被父窗口覆盖的内容。
但是如果只有子窗口设置了更新区域,系统不会给父窗口也设置。在无效化子窗口时,系统不会给父窗口发WM_PAINT(因为被覆盖住了,根本没有必要)。同样的,如果使被子窗口覆盖住的父窗口的部分区域无效化,系统也不会给父窗口发送WM_PAINT的。在这种情况下,无论子窗口还是父窗口都不会收到WM_PAINT消息。
父子窗口间的刷新,还受父窗口是否设置了WS_CLIPCHILDREN样式影响。
父窗口如果设置了WS_CLIPCHILDREN这个样式的话,当父窗口的更新区域被设置的时候,子窗口的更新区域不会被设置。父窗口作用在子窗口下面的任何绘图全部被裁减掉。
因此,当父窗口无效且收到WM_PAINT消息时,如果没有设置WS_CLIPCHILDREN样式,则所有子窗口都会在父窗口处理WM_PAINT之后收到WM_PAINT重绘消息;如果父窗口带有WS_CLIPCHILDREN样式,则不会引起子窗口重绘。
1.2 兄弟窗口间的刷新管理如果两个窗口重叠,则两个窗口都会收到WM_PAINT消息。他们收到WM_PAINT消息的顺序与z-index相反,即最上面的(z-order最高)的收到WM_PAINT消息最晚。
应用程序可以为窗口设置WS_CLIPSIBLING样式来避免兄弟窗口的绘制重叠。设置了这个,高z-order的窗口部分就会被上面的窗口裁减掉了,此部分被覆盖的区域就不会被刷新了。
结论1)WS_CLIPCHILDREN样式主要是用于父窗口,也就是说当在父窗口绘制的时候,父窗口上还有一个子窗口,那么如果设置了这个样式的话,子窗口所在区域父窗口就不负责绘制;
2)所有的overlapped和popup风格的窗口,都有WS_CLIPSIBLINGS属性。也就是说这类风格的窗口,你是去不掉WS_CLIPSIBLINGS样式的,这样就是它不会在其与兄弟窗口重叠的区域绘图;
3)WS_CLIPSIBLINGS样式只适用于同级窗口,实际上还需要和控件的叠放顺序(z order)配合使用才能看出明显的效果。
2、OnEraseBkGnd与OnPaint 2.1 常见的问题在OnEraseBkGnd中,如果你不调用原来缺省的OnEraseBkGnd只是重画背景则不会有闪烁。而在OnPaint里面,由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd函数,这时就和窗口缺省的背景画刷相关了。缺省的OnEraseBkGnd操作使用窗口的缺省背景画刷刷新背景(一般情况下是白刷),而随后你又自己重画背景造成屏幕闪动。
然而OnEraseBkGnd不是每次都会被调用的。如果你调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含调用BeginPaint的时候就会产生WM_ERASEBKGND消息,如果参数是FALSE,则不会重刷背景。
以上问题解决方法有三个半:
1)用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数。
2)用OnPaint实现,同时重载OnEraseBkGn,其中直接返回TRUE。
3)用OnPaint实现,创建窗口时设置背景刷为空。
4)用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样的函数。(不过这种情况下,窗口覆盖等造成的刷新还是要闪一下,所以不是彻底的解决方法)。
2.2 关于OnEraseBkGnd的返回值