`
testcs_dn
  • 浏览: 113776 次
  • 性别: 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技术应用到实际项目中,实现滚动条换肤功能。这有助于提升软件的用户界面美学,提高用户的满意度和使用体验。 总结而言,本示例主要展示了如何使用Hook技术来改变...

    C#控件滚动条换肤必备滚动条控制类

    本资源提供的"控件滚动条换肤必备滚动条控制类"是一个专门针对C#滚动条的自定义控制类,它允许开发者更加灵活地管理滚动条的行为和外观,包括滚动条的移动、换肤等功能。 1. **滚动条的基本属性和方法** - 属性:...

    CustomListCtrl 自绘列表控件 可任意更换滚动条图片

    在这个场景中,我们关注的是`CustomListCtrl`,这是一个自绘的列表控件,它扩展了MFC(Microsoft Foundation Classes)中的`CListCtrl`,提供更高级的功能,比如能够任意更换滚动条的图片。 `CListCtrl`是MFC库中...

    C# winform 重绘滚动条

    系统默认的滚动条样式可能无法满足所有设计要求,因此开发者经常需要重绘滚动条以实现独特的皮肤效果。本文将深入探讨如何在C# WinForm中实现滚动条的重绘。 首先,我们要理解WinForm中的控件绘制机制。Windows ...

    C# HScrollBar VScrollBar 水平滚动条美化皮肤控件

    `CustomScrollBar`这个项目很可能是自定义滚动条控件的核心实现。开发者通常会在这里创建一个自定义的UserControl,继承自System.Windows.Forms.Control类,并覆盖OnPaint方法来绘制自己的滚动条外观。在OnPaint方法...

    易语言滚动条模拟法换肤源码

    5. **皮肤更换机制**:为了实现换肤功能,源码可能会包含一个皮肤管理模块,允许用户选择不同的皮肤样式。这可能涉及到资源文件的加载、解析和应用。 6. **兼容性与性能优化**:在实现自定义滚动条时,需要注意与...

    clistctrl滚动条皮肤

    标题"clistctrl滚动条皮肤"指出,我们将探讨如何实现`CListCtrl` 滚动条的皮肤换肤功能。这涉及到对Windows API的深入理解和自定义控件的使用。`CListCtrl` 自身的滚动条并不是真正的操作系统级别的滚动条,它是一个...

    易语言滑块条换肤模块1.10

    9. **实例应用**:在实际项目中,滑块条换肤功能可以应用于音乐播放器、视频编辑软件、系统设置界面等多个场景,提升软件的美观度和用户体验。 10. **学习与调试**:对于易语言开发者来说,理解并熟练运用滑块条...

    支持换肤功能的窗口实例

    在VC++开发环境中,实现“支持换肤功能的窗口实例”是一个常见的需求,尤其是在创建具有用户友好界面的应用程序时。换肤功能可以让用户根据个人喜好调整应用程序的外观,提升用户体验。下面将详细介绍如何在VC++中...

    MFC换肤 MFC换肤

    - 兼容性测试:确保换肤功能在不同操作系统版本和屏幕分辨率下都能正常工作。 - 用户体验:换肤不仅仅是视觉上的改变,还要考虑用户体验,如按钮的点击反馈、滚动条的滑动效果等。 5. 示例代码: 假设使用了第三...

    实用界面换肤 界面自绘源码

    综上所述,"实用界面换肤 界面自绘源码"涵盖了C++ GUI编程中的多个高级主题,包括皮肤系统设计、自绘技术、滚动条定制和菜单处理。开发者需要有扎实的C++基础,熟悉至少一种GUI库,以及一定的图形绘制知识。通过深入...

    visiual C++开发典型模块大全--界面换肤模块

    在Visual C++编程环境中,开发一个具有界面换肤功能的应用程序是提高用户体验和软件个性化的重要方式。本模块大全将深入探讨如何在Visual C++中实现这一功能,为开发者提供全面的指导。 首先,理解界面换肤的核心...

    易语言源码窗口换肤模型.rar

    “Skin”文件可能是皮肤资源的集合,包含了各种不同风格的界面元素,如窗口背景、按钮、菜单、滚动条等的图片或样式定义。在易语言中,开发者通常会将这些资源打包成一个单独的文件,以便于管理和应用。通过读取和...

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

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

    Skinlib换肤库代码

    "Skinlib换肤库代码"就是这样一个工具,专为Windows应用程序设计,旨在帮助开发者轻松实现各种控件的皮肤更换功能。这个库支持多种常见的Windows控件,包括按钮、列表框、滚动条、菜单、输入框、复选框、列表视图...

    VB精品源码-优化版的经典换肤窗口

    4. **事件处理**:皮肤更换可能涉及到控件尺寸的改变,因此需要处理各种控件事件,确保在更换皮肤后控件的行为正常,例如点击响应、滚动条调整等。 5. **性能优化**:优化版的源码可能会特别关注性能,避免在频繁...

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

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

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

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

    MFC换肤文件

    3. **资源替换**:换肤涉及到替换MFC控件的默认资源,如按钮、滚动条、菜单等。开发者需要知道如何通过代码或皮肤引擎API替换控件的背景、边框、图标等资源。 4. **事件处理**:换肤后,UI交互逻辑也需要相应调整。...

    Svelte3.x网页聊天实例-svelte.js仿微信PC版聊天svelte-webchat.doc

    * 使用 svelte-scrollbar 实现滚动条组件 * 使用 svelte-layer 实现对话框组件 * 使用阿里巴巴字体图标库实现 Iconfont 图标 Svelte-Webchat 是一个功能完备的网页聊天系统,使用了最新的 Svelte 3.x 框架、...

Global site tag (gtag.js) - Google Analytics