一种革命性的自绘菜单实现

http://topic.csdn.net/u/20070830/15/d00dd81c-247f-43cd-b085-6d59ec1bc0de.html

无意中看到的文章,屌就一个字

 

一种革命性的自绘菜单实现


简介

自从发布了第一个版本的Windows   API后,改变(美化完善)通过Windows   GUI实现的界面元素的缺省外观这一举措,是Windows程序员所面临的一个永不停息的挑战。任何热忱于尝试改变Windows菜单外观的程序员(除了 简单的Owner-draw技术外)都必须充分掌握Windows   GUI的编程技术。

附注:为简明起见,下文将用“皮肤”一词表“改变默认界面”之意。

这里我想向大家展示的是一种全新的,原始性的对所有菜单加载皮肤的方法,它可以改变应用程序中任何菜单的绘制显示,包括(并不限于)以下类型:

.主菜单,
.所有通过右键(弹出)菜单(包括你自身定义的,以及系统内部的菜单,如IE右键弹出菜单和文件对话框FileDialog中的菜单),
.系统菜单(包括系统标题栏和任务条),
.下拉菜单,
.自制的或第三方owner-draw类型菜单(文件对话框的 'New '和 'Send   To '子菜单)。

挑战:

对于研发者来说,Windows菜单一直以来都是一个黑盒,所有的操作实现都埋藏在user32.dll内部。它仅允许开发人员将菜单定义为owner-draw模式,然后通过处理WM_MEASUREITEM和WM_DRAWITEM消息来绘制菜单条项。

这对于仅仅需要修改诸如菜单项的标准文字接口等一类简单需求来说是可以接受的(譬如用 'select   color '来选定菜单项的颜色。
但对于想重建整个菜单的外观的需求来说,明显是局限的。

特别是以下情况:
.要改变应用程序中所有菜单的外观,这需要我们获取每个菜单的句柄(你何不尝试获取Edit控件的菜单看看可行性)。
.要向所有含有驱动于WM_MEASUREITEM   和   WM_DRAWITEM   消息的菜单的窗口或控件插入代码。
.它没有提供非客户区域的绘制操作控制权。

最致命的是,不能更改系统定义的自制菜单外壳,因为无法知道该如何储存所需的数据。你无法处理一些已经定义好的owner-draw模式的菜单,如windows内部菜单等,因为一般我们很难获知它在内存的数据构造.也正正是这点,扼杀了开发者想适当重绘它们的念头。


解决方案:

Part   1
解决方案的创造性火花是怎么产生的呢,我也不知道.可能是正在浴室洗澡的时候(阿基米德洗澡时候发现了浮力的现象),抑或坐在树下被苹果扎中了脑袋,天晓 得。不过我做到了。我想,假若我可以将所有菜单的绘制由屏幕重定向到内存DC(设备环境)中,那加工的流水线就经过我这一站了。这样,在将它们搬到屏幕 前,就可以随心所欲的给整容了。说是容易,其路漫漫啊。

Part   2
方案的实现可以分成2个步骤:
        ×   在菜单即将显示的时候侦测出来。
        ×   针对该特定菜单做重绘。
       
前者相对简单一些。我们只需安装一个windows   hook   去侦测windows中所有菜单的创建就可以了。


附注:向还不太熟悉的朋友提示一下,windows中标识菜单的类名是 "#32768 "。
虽然在菜单创建的时候已经捕获到其窗口,但留下来的问题是,“我们怎么处置它?”,答案是子类化。在这里,我必须感谢Paul   DiLascia和他留下的子类化窗口遗产(CSubclassWnd类).CSubclassWnd是一个基于MFC的可以拦截和重写所有 windows窗口消息的类(即便我们不用费力自己创建窗口).所以,归纳起来说就是拦截所有菜单的窗口后对他们进行子类化,进而重写其默认的绘制部分操 作。

重写默认绘制

在程序设计上的不变法则是:一旦有了设计思路,就当付诸行动。但我遇到了难题。我在Windows   GUI编程中发现那里的变量有太多的遁词和变化(从Windows   95   ~   XP),不得不调遣非公开接口来寻找可行性的方案。
事实上,没有正规的接口能够解决所面临的问题。
无计可施之际,我的第一个工作唯有是跟踪侦测弹出菜单的时候会发出什么消息。然后想办法替换它们为我自己的代码实现。
非常幸运,我发现菜单的绘制机制亦算简单,下面将罗列必须要处理的消息:

WM_PRINT   -   请求绘制菜单的非客户与/或客户区域在DC设备环境上,并知DC句柄保存在wParam参数中(Windows   9x,   2K,   XP)。
WM_PRINTCLIENT   -   请求绘制菜单的客户区域。并知DC句柄保存在wParam参数中(Windows   9x,   2K,   XP)。
WM_PAINT   -   请求绘制菜单无效前景区域(Windows   9x)。
WM_ERASEBKGND   -   请求绘制菜单背景无效区域(Windows   9x)。
0x01e5   (未公开)   -   请求用一个内部方法绘制菜单项,句柄保存在wParam参数中(Windows   9x,   2K,   XP)。

最后一条消息(0x01e5)是整个项目中最奇趣也是最关键的部分。每当鼠标移动进出菜单时,windows将会向菜单发送该消息,
以便菜单及时重绘该子项。起初你可能会觉得该消息不占什么位置,但当你屏蔽或开启它的时候,你就知道它是必不可少的了。

通常,GUI事件触发后将会等事件执行完成后在按需求绘制。但菜单淡化处理并非如此。它是在事件发生后,通过系统时钟来执行(我相信是这样)。因此要用SetRedraw()方法来控制它的画出时间。

替换系统颜色
一旦我获取绘制的控制权,我则需要考虑如何替换体统颜色为我们所喜好的。
我一直在界面皮肤方面使用的一些具有心得的特效,于是我迸发了将他们移植到菜单绘制上来的念头,让它们来替换系统的呆板默认颜色绘制。

其他细节

  有一些敏锐的朋友将会指出,为什么没有用菜单句柄HMENU,这是我之前一直没有涉及到的.的确,使用菜单句柄可以更简单有效
  的实现菜单条项的绘制,只是它的可塑性相对会有所局限罢了。
  其实,获取菜单句柄也是我头痛的一个东西。window没有提供任何关于菜单窗口和菜单句柄的映射关联关系.若要获取菜单的句柄,
  我将截获WM_INITMENUPOPUP消息。并且,拦截到这一消息后,如果菜单已经创建可将消息赋予菜单窗口。或者拦截这一消息,
  在创建菜单子类后再赋予消息。我不能保证这样的侦测方法都是正确的,但它看起来一直工作得很好。也只能是我的不二选择。

代码的使用
      ×添加以下源文件到你的工程:
              附注:   在我的例程中,这些文件用一个叫做 'skinwindows '的文件夹整理好的,你大可不必如此。


                  o   CHookMgr   (hookmgr.h)   -   hook钩子模版类
                    o   CSkinBase   (skinbase.h/.cpp)   -   基本核心操作类
                    o   CSkinGlobals   (skinglobals.h/.cpp,   skinglobalsdata.h)   -   替换windows默认颜色类
                    o   CSkinMenu   (skinmenu.h/.cpp)   -   菜单重写类
                    o   CSkinMenuMgr   (skinmenumgr.h/.cpp)   -   hook菜单管理类
                    o   CSubclassWnd   (subclass.h/.cpp)   -   子类化窗口类(修改源于Paul   DiLascia的实现)
                    o   CWinClasses   (winclasses.h/.cpp)   -   侦测windows类的类
                    o   wclassdefines.h   -   定义了各种窗口类及其它类
                    o   skincolors.h   -   颜色   映射  

为了避免发生找不到配置文件的问题,加入了预定义NO_SKIN_INI。因为本文提供的代码源属于一个大系统,
该系统支持从文件中加载颜色配置信息,本应用不包括这个功能。

初始化皮肤hook引擎,在你继承于CWinApp类的初始化函数InitInstance()中加入:

#include   "skinmenumgr.h "   //   头文件与引入文件在同一路径

BOOL   CMyApp::InitInstance()
{
              :
              :
        CSkinMenuMgr::Initialize();
              :
              :        
}

请查阅源程序中的CSkinMenuMgr::Initialize()代码,里面有更详细的描述和选项。可以按照给定的宽度显示工具条,亦可将菜单边框设计为平面型的或斜面型的。

并且,你可以配置菜单显示的silderbar宽度并且选择菜单边框是否绘制的效果。
至于需要菜单如何绘制(包括菜单背景等),你可让你自己的类继承ISkinMenuRender类(该类在skinmenu.h文件里面定义),
并调用该类的静态成员函数CSkinMenu::SetRenderer()来激活它。这样你就可以通过自己重载的回调函数来定义自身的处理
了。(参考CSkinMenuTestDlg例程)

更多的完善

        *   没有处理好滚动菜单   (thanks   to   saltynuts2002)
        *   在Windows   CE   下不能运作(thanks   to   Jo??o   Paulo   Figueira)
        *   在xp下有点慢
        *   增加clip   box的数量可能会改善复制性能

版权
你可以不受限制地使用这里提供的代码,希望阁下能不要修改并使它成为你自己的,整个设计理念,将永远保留我的智慧性
知识产权。

源文:http://www.codeproject.com/menu/skinmenu.asp
下载源码也在此处可得。

你可能感兴趣的:(实现)