`
testcs_dn
  • 浏览: 105243 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

实现系统滚动条换肤功能

 
阅读更多


对于Windows系统中各种控件换肤功能,要数滚动条的换肤最难实现了,尤其是控件自带的系统滚动条,如Edit、ListBox、ListView、TreeView等自带的系统滚动条,要想实现其自定义的皮肤功能,用常规办法似乎都无法实现。

对于常规的皮肤定制一般都是通过定制WM_PAINT、WM_ERASEBKGND、WM_CTLCOLORxxx、NM_CUSTOMDRAW来实现。然而系统滚动条的绘制,常规的、很阳光的方法行不通,微软把一条康庄大道堵死了。根据我的观察测试,系统滚动条有许多的消息都对其执行了绘制,这包括WM_NCPAINT、WM_NCMOUSEMOVE、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上。我要说的是,这个拦截办法还真是有些邪门,有安全隐患,病毒就常干这种事情。对操作系统进行这类暴力破解式的修改,很容易引起系统防火墙或反病毒软件报错,Windows原则上不允许干这种事;其二,一旦使用此法的程序因异常而崩溃,原本对操作系统的修改没有还原,这可能会伤害到系统里的所有进程的稳定性,导致死机或运行异常。对于商业性的工程开发,往往很忌讳这一点,都倾向于追求稳定,通常是宁用拙法,不玩巧技;其三,此法有线程同步问题:因为正当你修改程序指令同时,若其他进程里的线程恰好执行到这里时候问题就会爆发出来,单CPU可能没啥问题,系统内存和CPU缓冲可以做到同步,但多核系统就难说了;另外这个方法还有移植性问题:在一种版本的系统中,通过拦截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来完成。

完成创建模拟窗口之后,你要给ListView安装一个你写的窗口过程,以拦截各种导致滚动条属性改变的种种消息:

LRESULT CALLBACK MyListViewProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

case WM_LBUTTONDOWN:

<wbr><wbr><wbr>......</wbr></wbr></wbr>

case WM_LBUTTONDBLCLK:

<wbr><wbr><wbr>......</wbr></wbr></wbr>

case WM_NCMOUSEMOVE:

<wbr><wbr><wbr>......</wbr></wbr></wbr>

case WM_NCMOUSELEAVE:

<wbr><wbr><wbr>......<wbr></wbr></wbr></wbr></wbr>

}

......

gOldListViewProc = (WNNPROC)::GetWindowLong(hListView, GWL_WNDPROC);

::SetWindowLong(hListView, GWL_WNDPROC, LONG(&MyListViewProc));//安装窗口过程

<wbr></wbr>

消息处理

首先我们要拦截WM_NCLBUTTONDOWN 和 WM_NCLBUTTONDBLCLK这两个消息,当你在滚动条上按下鼠标时,就立刻触发WM_NCLBUTTONDOWN,如果连续快速按两下鼠标就还有WM_NCLBUTTONDBLCLK。注意双击时只有一个WM_NCLBUTTONDOWN消息,而不是两个。第二次按鼠标会出现一个WM_NCLBUTTONDBLCLK。实际上这两个消息我们完全可以等而视之,做相同的处理。系统在处理这个两个消息时,都会在其内部处理中触发许多其他消息,其中有若干个WM_HSCROLL、WM_CAPTURECHANGED、WM_NCMOUSELEAVE。我们主要是要处理WM_HSCROLL,因为它最有价值。在你松开鼠标后,系统发送并处理完最后一个WM_HSCROLL之后,这才从WM_NCLBUTTONDOWN或WM_NCLBUTTONDBLCLK中返回:

if(msg == WM_NCLBUTTONDOWN || msg ==<wbr>WM_NCLBUTTONDBLCLK)</wbr>

{

<wbr><wbr><wbr>//注意默认处理会有N个WM_HSCROLL消息出现,要等你的按下拖拽操作完成后,这个调用才返回</wbr></wbr></wbr>

<wbr><wbr><wbr>//在这个调用内部,我估计系统会进入一种消息循环,因为按住左键之后,WM_NCMOUSEMOVE</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr>//和WM_NCLBUTTONUP都不再触发了。其内部估计是捕捉了WM_NCMOUSEMOVE消息,因之反复刷新滚动</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>//盒的位置,若有必要你可安装鼠标钩子,以捕捉鼠标移动消息,以及时刷新模拟窗口中滚动盒的</wbr></wbr></wbr>

<wbr><wbr><wbr>//的位置。若只是响应WM_HSCROLL消息,你可能觉得滚动盒的拖拽比较滞,不平滑。</wbr></wbr></wbr>

<wbr><wbr><wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>//SetWindowsHookEx(...);</wbr></wbr></wbr>

<wbr><wbr><wbr>LRESUTL code = ::CallWindowProc(gOldListViewProc, hListView, msg, wParam, lParam);</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr>//UnhookWindowsHookEx(...);</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>return code;</wbr></wbr></wbr>

}

<wbr></wbr>

我建议大家去微软的网站下载ControlSpy 2.0这个小工具,它用来监视控件的消息,这东西很有用。

下面我们再看WM_HSCROLL消息,这个消息一般是系统处理WM_NCLBUTTONDOWN或者WM_NCLBUTTONDBLCLK时产生的,但有时也可能是程序刻意发送的,和滚动条操作没有关系。

if(msg == WM_HSCROLL)

{<wbr></wbr>

<wbr><wbr><wbr>::CallWindowProc(gOldListViewProc, hListView, msg, wParam, lParam);</wbr></wbr></wbr>

<wbr><wbr><wbr>//1)读取滚动条的数据</wbr></wbr></wbr>

<wbr><wbr><wbr>//2)再在模拟窗口上绘制滚动条</wbr></wbr></wbr>

<wbr><wbr><wbr>//......</wbr></wbr></wbr>

<wbr><wbr><wbr>return 0;</wbr></wbr></wbr>

}

另外还有许多其他的消息可能导致滚动条属性的变化,如用户的键盘操作、鼠标滚轮操作可能导致滚动条的Thumb位置发生改变,这些操作分别导致WM_KEYDOWN和WM_MOUSEWHEEL,而系统又在这两个消息中执行滚动条的绘制,因此你必须截获它们,但处理方法同WM_HSCROLL,这里不再啰嗦了。

当我们没有按下鼠标左键时,只是简单地在滚动条移动鼠标时,我们会发现滚动条的按钮和Thumb都会自动高亮,其实这些变化都是系统在WM_NCMOUSEMOVE和WM_NCMOUSELEAVE中绘制完成的。比如当鼠标进入到Thumb上时,它就高亮了,当鼠标离开它,它又变灰色了,你要分别处理WM_NCMOUSEMOVE和WM_NCMOUSELEAVE这两个消息。注意只有ListView应用主题风格之后才可能有WM_NCMOUSELEAVE消息,传统风格的ListView就没有这个消息,只有WM_NCMOUSEMOVE消息,因此处理高亮还真有些麻烦。正是因为主题风格和传统风格的差异,因此我建议你也处理WM_THEMECHANGED消息,以识别不同的主题模式,实现不同高亮处理。看上面那个图,TreeView的滚动条是主题风格的,但ListView的滚动条是传统风格的,但我加了换肤功能。

另外,我们还应当拦截处理ListView的WM_NCPAINT,将滚动条的区域抠掉,不让它绘制滚动条。尽管滚动条被模拟窗口遮住了,但还是有必要这样做,以防止出现意外情况:

if(msg == WM_NCPAINT)

{

<wbr><wbr><wbr>HRGN wRgn = NULL;<wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr>RECT sbRect = GetScrollBarRect();//读取滚动条矩形的一个函数</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr>HRGN sRgn = ::CreateRectRgn(sbRect.left, sbRect.top, sbRect.right, sbRect.bottom</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>if(wParam == 1)</wbr></wbr></wbr>

<wbr><wbr><wbr>{</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>RECT wRect;</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>::GetWindowRect(hListView, &amp;wRect);</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>wRgn = ::CreateRectRgn(wRect.left, wRect.top, wRect.right, wRect.bottom););</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>wRgn = ::CombineRgn(wRng, wRgn, sRgn, RGN_DIFF);</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>}</wbr></wbr></wbr>

<wbr><wbr><wbr>else</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr>{</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>wRgn = (HRGN)wParam;<wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>wRgn = ::CombineRgn(wRng, wRgn, sRgn, RGN_DIFF);</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>}</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr>::DeleteObject(sRgn);</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>::CallWindowProc(gOldListViewProc, hListView, WM_NCPAINT, WPARAM(wRgn), 0);</wbr></wbr></wbr>

<wbr><wbr><wbr>if(wParam == 1)</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>::DeleteObject(wRgn);</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>return 0;</wbr></wbr></wbr>

}

另外,还要拦截WM_WINDOWPOSCHANGING消息,因为当ListView的位置、尺寸、Z秩序发生改变时要及时调整模拟窗口的位置、尺寸、Z秩序。为何要在WM_WINDOWPOSCHANGING中进行,而不选择在WM_WINDOWPOSCHANGED或WM_MOVE或WM_SIZE中进行呢?因为在WM_WINDOWPOSCHANGING中处理,可让模拟窗口先于ListView调整自己的位置、尺寸和Z,可避免一些绘制问题。实际我在FreeCL中既用了WM_WINDOWPOSCHANGING,也用到WM_WINDOWPOSCHANGED两个消息。在处理WM_WINDOWPOSCHANGING时调整位置、尺寸、Z秩序,在处理WM_WINDOWPOSCHANGED时,强制重绘模拟窗口。

末了还要说明一点的是,系统可能因为用户改变控件尺寸导致其滚动条自动消失或自动显示,或者调整滚动条的可用状态,如你拉宽支持多行显示的Edit控件,会导致滚动条箭头按钮变灰,Thumb消失。这些动作通常都是在WM_WINDOWPOSCHANGED中完成的,因此这个消息也需要拦截处理:

if(msg == WM_WINDOWPOSCHANGED)

{

<wbr><wbr><wbr>LRESULT lReturn = ::CallWindowProc(gOldListViewProc, hListView, WM_WINDOWPOSCHANGED,</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>wParam, lParam);</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>if(::GetNextWindow(hBuddy,<span style="word-wrap:normal; word-break:normal; line-height:21px">GW_HWNDNEXT</span>) != hListView)</wbr></wbr></wbr>

<wbr><wbr><wbr>{<wbr><wbr>//调整模拟窗口的Z-Order,确保其紧贴ListView之上</wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>HWND hAbove = ::GetNextWindow(hListView, GW_HWNDPREV);</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>UINT flag = SWP_NOACTIVATE|SWP_NOREDRAW|SWP_NOMOVE|SWP_NOSIZE|SWP_NOSENDCHANGING;<br><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>::SetWindowPos(hBuddy, hAbove?hAbove:HWND_TOP, 0, 0, 0, 0, flag);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>}</wbr></wbr></wbr>

<wbr><wbr><wbr><wbr>//测试滚动条的显隐状态是否改变,并因之调整模拟窗口的显隐状态</wbr></wbr></wbr></wbr>

<wbr><wbr><wbr>//如果滚动条仍旧显示或滚动条某些元素的显示状态有变化或控件位置、尺寸可能改变,</wbr></wbr></wbr>

<wbr><wbr><wbr>//强制重绘模拟窗口</wbr></wbr></wbr>

<wbr><wbr><wbr>//......</wbr></wbr></wbr>

<wbr><wbr><wbr>return lReturn;</wbr></wbr></wbr>

}

最后要处理WM_SHOWWINDOW和WM_DESTROY,当ListView隐藏时让模拟窗口也随之消失;当它显示时,让模拟窗口也随之显示。当ListView销毁时会触发WM_DESTROY,这时也需要销毁模拟窗口,这很重要。<wbr></wbr>

总的来讲,模拟法是蛮麻烦的,但比较安全,可靠性高。对开发者来讲,都是对消息进行特定处理,是常规手段,对最终用户来讲,用此法实现的滚动条自绘,完全能够满足要求,具有充分的自由度,甚至可以对滚动条添加更多特定的功能,比如可以在上面添加其他按钮,就算是加动画、加广告也都是可以的。


文章和截图来自这里:http://blog.sina.com.cn/s/blog_4c3538470100gews.html


分享到:
评论

相关推荐

    对窗口内建滚动条换肤

    使用HOOK技术对于窗口内建的滚动条进行换肤的一个DEMO。不是通过创新新的窗口实现的哦。

    微软FreeCL开源免费的C 编译器源码.rar

     1)新增了系统滚动条皮肤功能(现Edit、ListBox、TreeView、ListView、Window均支持滚动条换肤);  2)增强了控件滚动条皮肤功能;  3)修正了ListView表头因响应键盘方向键消息和鼠标滚轮消息而不能正确刷新皮肤的...

    金品皮肤dll控件Skinsharp+V1.0.6.6

    在滚动条换肤上,SkinSharp做到了所有控件内置滚动条的换肤,并且不修改控件任何风格和属性,完美兼容各个控件。在菜单换肤上,SkinSharp采用独特的技术对所有菜单实行换肤,没错,是所有的菜单,包括IE控件内部菜单...

    react-admin system solution : react 后台管理系统解决方案

    换肤(全局功能,暂时只实现了顶部导航的换肤,后续加上其他模块) 导航菜单 顶部导航(菜单伸缩,全屏功能) 左边菜单(增加滚动条以及适配路由的 active 操作) UI 模块 按钮(antd 组件) 图标(antd 组件并增加彩色表情符...

    最好的asp CMS系统科讯CMSV7.0全功能SQL商业版,KesionCMS V7.0最新商业全能版-免费下载

    本系统是一款由文章、图片、下载、分类信息、商城、求职招聘、影视、动漫(flash)、音乐、广告系统、个人/企业空间、小型互动论坛、友情链接、公告、调查等20多个功能模块,并集成自定义模型、自定义字段等功能组合而...

    超美播放器

    还有,自绘的ListBox滚动条,不可不看! 这个播放器的独特在于,标题区的外观与众不同!同类产品比较: 1。Windows2000的 CD Player,虽然实现了标题“换肤”,但它是先把标题隐藏起来,然后自己在客户区画了一...

    VC实现炫眩qq界面的模拟(附源码)

    界面换肤 美化 异形窗口 vista 样式 透明窗体 自绘控件 vc vb delphi 编程 源码 代码 例子 磨沙 半透明 开发包 免费 破解 button dialog frame XP 下载 自绘按钮 菜单 标题栏 滚动条 listctrl treeview listview ...

    vc++ 应用源码包_1

    任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器的源码,解决CListCtr刷新时滚动条跳到开始处。 VC++实现网络连接查看器源码 非常好的一个实例,把网络连接的UDP/TCP都插入到CList控件中显示出来。 VC++...

    vc++ 应用源码包_2

    任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器的源码,解决CListCtr刷新时滚动条跳到开始处。 VC++实现网络连接查看器源码 非常好的一个实例,把网络连接的UDP/TCP都插入到CList控件中显示出来。 VC++...

    vc++ 应用源码包_3

    任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器的源码,解决CListCtr刷新时滚动条跳到开始处。 VC++实现网络连接查看器源码 非常好的一个实例,把网络连接的UDP/TCP都插入到CList控件中显示出来。 VC++...

    vc++ 应用源码包_6

    任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器的源码,解决CListCtr刷新时滚动条跳到开始处。 VC++实现网络连接查看器源码 非常好的一个实例,把网络连接的UDP/TCP都插入到CList控件中显示出来。 VC++...

    高仿QQ2014,MDI,主题换肤

    1.修复SkinPanel滚动条不显示的问题。 2.修复SkinPanel右下边框不显示的问题。 3.修复好友列表头像闪烁的一些BUG。 4.修复SkinMain异形窗体点击任务栏最小化操作。 5.加入内置工具类FastBitmap,此类用来快速Bitmap...

    vc++ 应用源码包_5

    任务管理器应该大家都很熟悉,论坛里也有好多的任务管理器的源码,解决CListCtr刷新时滚动条跳到开始处。 VC++实现网络连接查看器源码 非常好的一个实例,把网络连接的UDP/TCP都插入到CList控件中显示出来。 VC++...

    CMS轻量级系统

    后端支持天梯蓝和天梯红换肤功能。 4、项目技术分层明显,用户可以根据自己的业务模块进行相应地扩展,很方便二次开发。 核心框架:Spring Framework 4.2.5.RELEASE 安全框架:Apache Shiro 1.3.2 视图框架:...

    vc++ 开发实例源码包

    MFC换肤完全贴图实现 如题。 Smile简体版 很简单,只能播放mp3格式的音乐。 功能: --------------------------------------- --------关闭-- 打开----最小化------- --------------------------------------- ...

    Delphi7编程100例

    实现OutLook滚动工具栏效果 在下拉列表框中显示树形视图 自定义系统的About项 修改系统级菜单 实现透明窗体效果 爆破特技窗体 只允许建立一次子窗体的MDI程序 从外部DLL中调用子窗口 新颖的资源...

    阿赛企业建站系统 AsaiCoE v8 ASP.rar

    30、高兼容二级下拉导航:支持火狐、IE等各种浏览的导航条下拉二级导航功能,可以通过修改CSS实现各种不同的效果,支持快速换肤;31、美化解释框功能:美化前台鼠标放上去会出现优美的ALT解释功能,替代系统自带的...

    OnionPrac 2010 Build 15 新旗舰

    ④增添了换肤功能 ⑤可以合并、分割题库、导出题库答案,使答案文件从题库中独立出来 ⑥增强了错题录的功能,可以保存反思笔记 Build 9:增添了快速编辑功能 Build 10: ①增添了大屏幕,以适应班级投影的需求 ...

    Delphi编程100例

    实现OutLook滚动工具栏效果 在下拉列表框中显示树形视图 自定义系统的About项 修改系统级菜单 实现透明窗体效果 爆破特技窗体 只允许建立一次子窗体的MDI程序 从外部DLL中调用子窗口 新颖的资源管理器界面 如何生成...

    《Delphi7编程100例》代码

    ToolBar工具栏控件的使用动态建立主菜单选项窗口界面的动态分隔条动态设置选项卡页面在标题栏中自定义按钮窗体开合窗帘效果Windows XP界面效果实现OutLook滚动工具栏效果在下拉列表框中显示树形视图自定义系统的...

Global site tag (gtag.js) - Google Analytics