本文讲述如何定制控件的背景颜色和背景位图的技巧。
首先要说一下控件的绘制过程:当控件的某个区域需要重绘时,都会触发WM_ERASEBKGND和WM_PAINT消息。比如控件的某个区域被另一个程序的窗口挡住了,而后那个窗口又被移走了,这时控件被挡住的内容就需要重新绘制了。
第一步:系统向控件发送WM_ERASEBKGND消息以实现背景的擦除工作(有时不发送,比如用户可能调用InvalidateRect(),其参数却指定不擦除背景,这样就没有这个消息);
第二步:系统向控件的窗口过程发送WM_PAINT消息,控件执行处理这个WM_PAINT消息时会有选择地触发后面三个步骤的动作;
第三步:对于有些标准控件,如Button、Edit、ListBox、ScrollBar、Static控件,它还会向父窗口发送WM_CTLCOLORxxx(WM_CTLCOLORBUTTON、WM_CTLCOLOREDIT、WM_CTLCOLORSTATIC、WM_CTLCOLORLISTBOX、WM_CTLCOLORSCROLLBAR等)的消息,这些消息返回一个刷子句柄,系统拿这个刷子句柄进一步涂刷自己的背景。另外还发现Trackbar也会向父窗口发送WM_CTLCOLORSTATIC消息,TreeView在某些情况下也有,不过我没有看到微软在什么地方对这一点作说明;我常常发现很多人处理这个消息时,喜欢给系统返回一个NULL_BRUSH的空刷子,以为这样系统就不会把前面步骤画好的背景覆盖掉,其实不一定的,有些控件不覆盖,有些就有问题,像Trackbar就是如此,要小心。
第四步:对于菜单和许多标准控件,如Button、Edit、ListBox、Static、ComboBox它可能会向父窗口发送WM_MEASUREITEM和WM_DRAWITEM消息,另外通用控件Tab、StatusBar、ListView、Header也可能会有WM_DRAWITEM消息;但对于多数通用控件,如TreeView、ListView、Rebar、Trackbar、Toolbar等,它会向父窗口发送许多其ID为NM_CUSTOMDRAW的WM_NOTIFY消息。对于这两种消息,实际要求用户在已经涂刷好的背景之上再执行自己的绘制工作;
第五步:当控件的WM_DRAWITEM或者WM_NOTIFY消息没有被用户处理时,系统会亲自执行自己的默认绘制工作,把控件画出来,这一步没有办法重载。
知道了这些步骤,大概我们心中已经了然,知道如何定制控件的背景颜色和背景位图了。一般情况下我们定制第一步、第三步实现自己的特殊背景,定制第四部实现控件本身的特殊绘制。甚至我们可以整个重载控件第一步的WM_ERASEBKGND消息和第二步的WM_PAINT消息,控件背景和控件绘制全部自己搞定,没人说这样做不行。不过要注意,当自己实现WM_PAINT消息的重载处理时,后面3个步骤就都不发生了。
定制颜色倒是很简单,根据控件的类型处理WM_ERASEBKGND、WM_CTLCOLORxxx、WM_DRAWITEM、WM_NOTIFY消息了,一般情况下,定制WM_ERASEBKGND和WM_CTLCOLORxxx就可以了;对于背景位图很多控件却特别麻烦,像ListBox,你把背景位图涂刷好,结果因为用户操作滚动条或鼠标滚轮或按方向键,背景位图也发生滚动,这就不得不重绘位图,对于背景颜色就没有这个问题,不管怎么滚动,颜色还是那个颜色,位图就不行,需要自己重载发生滚动操作的各种消息以实现位图重绘。微软似乎并不假设你会修改控件的背景位图,它没有对这个情况作准备,总是毫不犹豫的对控件画布执行Scroll操作。真正实现背景位图的方法常常迫使我们要拦截那些导致窗口内容发生滚动的各种操作,因此位图背景的功能实现也总让人觉得不怎么规范、不那么可信。
最后要说明的是:如果父控件还包含背景透明的子控件,你应当重载父控件的WM_ERASEBKGND消息,否则那些透明背景的子控件可能就没有正确的背景内容。
上面这些步骤是我个人的理解,不一定对哦!仅供参考。