看到论坛上很多人问到怎么把ListView控件的列表头不包含列表项的最后那一部分也进行重绘,看起来更美观一些,但是基本都没能解决,今天就发这篇文章,提供一种解决方法吧。具体文章看 C# WinForm控件美化扩展系列之ListView。
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);
rect.Left = left;
return Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);
}
现在再来看看HeaderNativeWindow类的完整代码:
private class HeaderNativeWindow : NativeWindow,IDisposable
{
private ListViewEx _owner;
public HeaderNativeWindow(ListViewEx owner)
: base()
{
_owner = owner;
base.AssignHandle(owner.HeaderWnd);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0xF || m.Msg == 0x47)
{
IntPtr hdc = GetDC(m.HWnd);
try
{
using (Graphics g = Graphics.FromHdc(hdc))
{
Rectangle bounds = _owner.HeaderEndRect();
Color baseColor = _owner.HeadColor;
Color borderColor = _owner.HeadColor;
Color innerBorderColor = Color.FromArgb(200, 255, 255, 255);
_owner.RenderBackgroundInternal(
g,
bounds,
baseColor,
borderColor,
innerBorderColor,
0.45f,
true,
LinearGradientMode.Vertical);
}
}
finally
{
ReleaseDC(m.HWnd, hdc);
}
}
}
#region IDisposable 成员
public void Dispose()
{
ReleaseHandle();
_owner = null;
}
#endregion
}
再来看看对ListView创建和销毁句柄的两个方法OnHandleCreated和OnHandleDestroyed的重写。
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (_headerNativeWindow == null)
{
if (HeaderWnd != IntPtr.Zero)
{
_headerNativeWindow = new HeaderNativeWindow(this);
}
}
}
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
if (_headerNativeWindow != null)
{
_headerNativeWindow.Dispose();
_headerNativeWindow = null;
}
}
最后来看看效果:
声明:
本文版权归作者和CS 程序员之窗所有,欢迎转载,转载必须保留以下版权信息,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
作者:Starts_2000
出处:CS 程序员之窗 http://www.csharpwin.com。
你可以免费使用或修改提供的源代码,但请保留源代码中的版权信息,详情请查看:
CS程序员之窗开源协议 http://www.csharpwin.com/csol.html。