前面的一篇文章C# WinForm控件美化扩展系列之ListView实现了隔行不同颜色和对列表头进行了美化,但遗憾的是对列表的最后的不包含列头的部分没有进行重绘,主要原因是上次没时间处理,因为处理那一部分是比较困难的,需要花不少时间,今天总算有时间补偿上次的遗憾了。
网上对列表控件(ListView)的美化也很少有对不包含列表部分的重绘,C#的就更加少了,但是很多人都在各个论坛问到这个东西,也少有人回答,主要是这个用到API方面的东西比较多,也不是太好处理,今天把这篇文章写出来,也算是弥补一下这方面的资料吧,也是告诉大家,其他语言能实现的C#也都可以实现,只要找对方法就行了,好了,废话不多说了,进入正题。
ListView其实由两部分组成的,它包含了一个Header部分,用SPY++看看就知道了,要实现对列表最后一部分的美化,直接重写ListView的WndProc方法,截取WM_PAINT或者WM_NCPAINT消息都是不能对他进行很好的处理的,我们需要截取这个Header的消息才行,这点是至关重要的。要怎么截取他的消息,其实前面实有一篇文章C# 实现只能输入数字的ComboBox控件已经用到过这种方法了,以后写的控件可能会经常看到这种方法。第一步就是得到Header的句柄;第二步,继承NativeWindow,实现一个HeaderNativeWindow类,把Header的句柄分配给他。第三步,在HeaderNativeWindow类中重写NativeWindow的WndProc方法,然后进行相应的消息处理。在这里,我对WM_PAINT(0xF)进行了处理,在这个消息中进行了重绘。但是,我测试的时候发现,当改变ListView的大小的时候,变大没问题,还是正常的绘制,但是变小的时候,Header没有收到WM_PAINT消息,所以就没有重绘,但是可以收到WM_WINDOWPOSCHANGED(0x47)消息,所以我就在收到WM_WINDOWPOSCHANGED消息的时候也进行了重绘。第四步,当ListView创建句柄的时候,创建一个HeaderNativeWindow,让它可以截取Header的消息。当ListView销毁句柄时,HeaderNativeWindow也要释放Header的句柄。
方法有了,但是在重绘的时候我们需要知道需要绘制部分的大小和位置,看起来很简单,就是用Header的宽度减去最后一个列表头的最右边所在的位置,就得到要绘制部分的宽度了,高度就是列表头的高度,但是实现起来还是有点麻烦的。先发送一个HDM_GETITEMRECT到Header,获取最右边的列表头的位置和大小,然后再获得Header的大小,这样就可以计算出需要绘制部分的大小和位置了。看看代码:
private Rectangle HeaderEndRect()
{
RECT rect = new RECT();
IntPtr headerWnd = HeaderWnd;
SendMessage(
headerWnd, HDM_GETITEMRECT, ColumnAtIndex(ColumnCount - 1), ref rect);
int left = rect.Right;
GetWindowRect(headerWnd, ref rect);
OffsetRect(ref rect, -rect.Left, -rect.Top);