文章和截图来自这里:http://blog.sina.com.cn/s/blog_4c3538470100gews.html对于Windows系统中各种控件换肤功能,要数滚动条的换肤最难实现了,尤其是控件自带的系统滚动条,如Edit、ListBox、ListView、TreeView等自带的系统滚动条,要想实现其自定义的皮肤功能,用常规办法似乎都无法实现。对于系统滚动条的绘制,根据观察测试,它有许多的消息都对其执行绘制,这包括WM_NCPAINT、WM_MOUSEMOVE、WM_NCMOUSELEAVE、WM_HSCROLL、WM_VSCROLL、WM_KEYDOWN、WM_MOUSEWHEEL等等,这些消息中有些可以定制,但有些没法定制。比如WM_HSCROLL就无法定制,如果我们处理这个消息,就必须自己控制滚动条的范围、位置、翻页值,而这些值我们通常无法得到,微软根本没有告诉我们,对于不同的控件它操控滚动条到底采用什么策略,不晓得;假若我们不去定制这个WM_HSCROLL,而默认的处理它又执行滚动条的绘制,这真是进退无路,叫人束手无策。在网上反复搜索,自己也仔细研究,基本有两中办法来实现系统滚动条换肤,一种方法是HOOK API,也就是拦截API的办法,还有一种是模拟法。下面分别简述一下这个两个方法。拦截API,实际是通过修改操作系统的API入口。因为系统绘制滚动条是通过各种绘制函数来实现的,比如SetScrollInfo(),基本系统通过这一类函数实现滚动条的绘制,对这个API进行拦截,也就是我们自己写一个SetScrollInfo(),来亲自实现滚动条的绘制,以便由此实现滚动条的自定义绘制,实现我们想要的各种风格的皮肤外观。API的拦截有两种:一种是修改系统所装入内存可执行模块的导入地址,替换成我们所写的伪API,使API调用能够自动跳转到这个伪API上;还有一种是直接修改API函数首地址处的若干机器指令,使之在执行到这个API时能自动跳转到我们所写的伪API上。我要说的是,这个拦截办法还真是有些邪门,有安全隐患,病毒就常干这种事情。对操作系统进行修改,会影响到系统里的所有进程的稳定性,而且这个方法也有移植性问题。在一种版本的系统中,通过拦截API有效,在另一种版本的系统中,就不见得还有效,毕竟微软实现滚动条的绘制,它没规定一定就用哪个函数来实现,也许新系统中它另有高招了。下面我要详细讲的是模拟法了,这个办法不用拦截API,也不用消息钩子什么的,所有的技术实现都约束在系统所允许的范围,咱一丝不苟的当遵纪守法的良民。所谓模拟法就是在系统滚动条的区域放置一个模拟窗口,这个窗口专门用于绘制系统滚动条。当然我们也要拦截带滚动条控件的若干消息,以确保模拟窗口的绘制正确。下面以ListView控件的水平滚动条为例,进行说明。
首先我们要在滚动条的区域上创建一个模拟窗口,恰好覆盖滚动条:
HWND hListView = ...;//ListView窗口的句柄
HWND pWnd = ::GetParent(hListView);
HWND hBuddy = ::CreateWindowEx(WS_EX_NOACTIVATE, "Buddy_Window", "", WS_CLIPSIBLINGS|WS_DISABLED|WS_CHILD, 0, 0, 0, 0, pWnd, NULL, gModule, NULL);
Buddy_Window是你注册的一个窗口类。注意,窗口一定要有WS_DISABLED风格,这是个关键,这个风格能够确保鼠标操作能够透过模拟窗口,而直接操控到所覆盖的滚动条,它不接受任何鼠标键盘的输入。另外读取滚动条的矩形以及它的各个元素的可视、可用、压下等状态可通过GetScrollBarInfo()这个API来完成,不过我要说的是这个API有些Bug,到时大家可下载FreeCL 2.03版源码,里头修正了这个问题。完成创建模拟窗口之后,你要给ListView安装一个你写的窗口过程,以拦截各种导致滚动条属性改变的种种消息:LRESULT CALLBACK MyListViewProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
case WM_LBUTTONDOWN:
    ......
case WM_LBUTTONDBLCLK:
    ......
case WM_NCMOUSEMOVE:
    ......
case WM_NCMOUSELEAVE:
    ...... 
}......gOldListViewProc = (WNNPROC)::GetWindowLong(hListView, GWL_WNDPROC);
::SetWindowLong(hListView, GWL_WNDPROC, LONG(&MyListView));//安装窗口过程 消息处理:首先我们要拦截WM_NCLBUTTONDOWN 和 WM_NCLBUTTONDBLCLK这两个消息,当你在滚动条上按下鼠标时,就立刻触发WM_NCLBUTTONDOWN,如果连续快速按两下鼠标就还有WM_NCLBUTTONDBLCLK。注意双击时只有一个WM_NCLBUTTONDOWN消息,而不是两个。第二次按鼠标会出现一个WM_NCLBUTTONDBLCLK。实际上这两个消息我们完全可以等而视之,做相同的处理。系统在处理这个两个消息时,都会在其内部处理中触发许多其他消息,其中有若干个WM_HSCROLL、WM_CAPTURECHANGED、WM_MOUSELEAVE。我们主要是要处理WM_HSCROLL,因为它最有价值。在你松开鼠标后,系统发送并处理完最后一个WM_HSCROLL之后,这才从WM_NCLBUTTONDOWN或WM_NCLBUTTONDBLCLK中返回:
if(msg == WM_NCLBUTTONDOWN || msg == WM_NCLBUTTONDBLCLK)
{
    m_Pressed = TRUE;
     //注意默认处理会有N个WM_HSCROLL消息出现,要等你的按下拖拽操作完成后,这个调用才返回
    LRESUTL code = ::CallWindowProc(gOldListViewProc, hListView, msg, wParam, lParam);     m_Pressed = FALSE;
} 下面我们看WM_HSCROLL消息,这个消息一般都是WM_LBUTTONDOWN或者WM_LBUTTONDBLCLK产生的,但有时也可能是程序刻意发送的,和滚动条操作没有关系。
if(msg == WM_HSCROLL)

    1)读取滚动条的数据
    2)再在模拟窗口上绘制滚动条
}
另外还有许多其他的消息可能导致滚动条属性的变化,如用户的键盘操作、鼠标滚轮操作可能导致滚动条的Thumb位置发生改变,这些操作分别导致WM_KEYDOWN和WM_MOUSEWHEEL,但处理方法同WM_HSCROLL,这里不再啰嗦了。当我们没有按下鼠标左键时,只是简单的在滚动条移动鼠标时,我们会发现滚动条的按钮和Thumb都会自动高亮,其实这些变化都是系统在WM_NCMOUSEMOVE和WM_NCMOUSELEAVE中绘制完成的。比如当鼠标进入到Thumb上时,它就高亮了,当鼠标离开它,它又变灰色了,你要分别处理WM_NCMOUSEMOVE和WM_NCMOUSELEAVE这两个消息。注意,只有ListView应用主题风格之后才可能有WM_NCMOUSELEAVE消息,传统风格的ListView就没有这个消息,只有WM_MOUSEMOVE消息,因此处理高亮还真有些麻烦。看上面那个图,TreeView的滚动条是主题风格的,但ListView的滚动条是传统风格的,但我加了换肤功能。两外我们还应当拦截处理ListView的WM_NCPAINT,将滚动条的区域抠掉,不让它绘制滚动条。尽管滚动条被模拟窗口遮住了,但还是有必要这样做,以防止出现意外情况:if(msg == WM_NCPAINT)
{
    HRGN wRgn = NULL; 
    RECT sbRect = GetScrollBarRect();//读取滚动条矩形的一个函数
    HRGN sRgn = ::CreateRectRgn(sbRect.left, sbRect.top, sbRect.right, sbRect.bottom
    if(wParam == 1)
    {
        RECT wRect;
        ::GetWindowRect(hListView, &wRect);
        wRgn = ::CreateRectRgn(wRect.left, wRect.top, wRect.right, wRect.bottom);
        wRgn = ::CombineRgn(wRng, wRgn, sRgn, RGN_DIFF);
    }
    else
        wRgn = (HRGN)wParam; 
    ::DeleteObject(sRgn);
    ::CallWindowProc(gOldListViewProc, hListView, WM_NCPAINT, WPARAM(wRgn), 0);
    if(wParam == 1)
        ::DeleteObject(wRgn);
    return 0;
}最后要拦截WM_WINDOWPOSCHANGING消息,因为当ListView的位置、尺寸、Z秩序发生改变时要及时调整模拟窗口的位置、尺寸、Z秩序。为什么要在WM_WINDOWPOSCHANGING中进行呢?而不选择WM_WINDOWPOSCHANGED或WM_MOVE或WM_SIZE中进行呢?因为在WM_WINDOWPOSCHANGING中处理,可以让模拟窗口先于ListView调整自己的位置、尺寸和Z,可以避免一些绘制上的问题,不信你试试看啦。最后要处理WM_SHOWWINDOW,当ListView消失的时候让模拟窗口也随之消失;当它显示时,让模拟窗口也随之显示,这很重要。