Skin技术实现框架

Skin技术实现框架

前言

嘿嘿,估计今天写不了多少,就叫前言吧,下次再写原理
说 到skin技术,大家都不会陌生,最早接触这东西,可能是winamp吧,可以灵活的更换界面风格,非常的花哨。后来使用skin的软件就越来越多了,毕 竟做一个漂亮的界面对软件还是很重要的。虽然Windows标准界面越做也是越花哨,但总不能满足人的胃口。有一个自己特殊的华丽界面总是值得夸耀的,看 看MSN Explorer,Media Player, RealOne...。实现这种定制的外观方法很多,早期的Skin技术都需要程序本身做许多处理,基本就是贴一些图片在界面上,然后通过换图片获得不同 的视觉效果,象winamp就是这样的。这种方式其实非常灵活,可以实现想要的任何效果,缺点是编码实现起来太麻烦了。

随着希望有自己特定Skin的软件越来越多,就出现了专门的Skin插件,这个比较有名的是WindowBlindsActiveSkin,我所知道和用过的就这俩,也不知道是不是最有名的, 这些产品一般都是提供一个COM组件,需要Skin支持的程序创建这个COM组件,然后调用几个方法,就可以使自己的程序外观完全改变,甚至可以在运行时 动态改变外观。这样的组件包使用起来非常的方便,不需要编程者对skin技术有任何的了解。缺点么,主要是要收费的,当然我们可以用破解版,我当初用的 WindowBlinds组件就是我们公司一大拿花了一晚上弄出来的破解版。收费只是一方面,用人家的劳动成果是应该给钱的,真正的问题在于往往还不能满足要求。为了弄出100%符合自己要求的Skin,当然就只能自己写了。

从 今天起我就来讲讲怎么写这样的Skin插件。2002年的时候写了一个这样的插件,当初的目的是在PC机上模拟Mac的效果。一开始用 windowblinds组件,总是不能令人满意,终于说还是自己写吧,就开始写了。花了一个多月的时间吧大概,本来已经写的差不多了,后来由于商务上的 原因,居然项目取消了,白干了。当然对于技术人员没有什么白干的东西,工资没少发,技术上提高了

前两天有人问我关于消息钩子的问题,忽然想起前年写的这个东西了(前年?!怎么过的这么快,老了)。看看当初的代码都还在,而且这东西的设计,当初颇让我自己得意的,现在看看,也确实是不错的。与其让它躺在硬盘上腐烂,还不如拿出来晾晾,说不定对同学们有帮助,没准有兴趣的人一起弄个OpenSource的项目继续写也是不错的

设计目标

前 言差不多了,下面写点设计目标。这东西最重要的设计目标是使用方便,已有的程序创建一个COM对象,调一个方法就可以把界面外观全部改成Mac风格的。另 外一个目标是要有扩展性,因为另外存在要在Windows98上模拟Windows XP界面效果的需求,以后还可以出现模拟其他系统的要求。所以,基本的设计是定义一个统一的接口,然后做不同的实现。每一种实现单独做在一个COM DLL中,调用方选择一个CLSID创建对象就行了。干脆把接口的定义先贴出来吧

     interface ISkinX : IUnknown

     {

         [helpstring("Install Skin hook")] HRESULT InstallSkin([in] long lThreadID);

         [helpstring("Uninstall Skin hook")] HRESULT UninstallSkin();

     };


调用InstallSkin安装Skin,UninstallSkin卸掉Skin,lThreadID是线程ID,这个后面会解释。
今天就到这里吧,最后贴几个图片,看看效果先

原理

上次基 本上是些介绍,也就是废话,今天讲讲实现Skin的基本原理吧。要实现自己独特的界面,方法有很多啦,上次也说过,这里只讲一种,就是通过消息钩子改变已 有控件的外观。这种方法的好处是可以不必修改程序已经完成的标准界面,只要把钩子函数挂上,所有的界面就都变了,使用起来非常方便。这里的基本原理就是下 面这个调用:
SetWindowsHookEx(WH_CALLWNDPROC, HookProc, 0, lThreadID);
WH_CALLWNDPROC钩子可以截获所有线程ID为lThreadID的线程内的窗口消息,这样我们就有机会处理这些消息。
但 是,光截获消息还不够,我们还必须知道这些消息是谁发出的,Button和EditBox发出的相同消息显然必须得到不同的处理。幸运的是,从消息的参数 里,我们可以得到窗口句柄,而通过窗口句柄,我们可以得到窗口类。这里说的窗口类可不是C++的类,而是Windows系统中的窗口类名。例如,按钮的窗 口类是“Button”,组合框的窗口类是“ComboBox”...这些在MSDN里面都可以找到的,另外,还有一些文档中不存在的窗口类名,比如对话 框,有一个叫“#32770”的类名,而菜单,实际上也是一个窗口,其类名是“#32768”。有意思吧,有了这些信息,我们就可以区分不同窗口进行处理 了。
至于处理些什么消息,显然最重要的是WM_PAINT消息。这样我们可以重载系统默认的绘图方式,而把控件窗口画成我们想要的样子。但是只处 理WM_PAINT消息也是不够的,因为控件的样式不是一成不变的,看看WindowsXP的显示效果,以按钮为例,有很多种样式,普通样式、鼠标在按钮 上的样式、鼠标按住按钮的样式、鼠标按住按钮又移动到按钮外的样式...... 为了实现动态的炫目的Skin效果,我们还需要截取一些其他消息,例如鼠标消息。下载的代码里有Mac按钮的一个实现,看一下就知道了。
原理就这么多了,好像不是很复杂是吧,不过知道了原理和能写出实际工作的代码,还是有很大区别的。还有非常关键的设计和编码,这些,留等下次在说吧,今天就到这里,就到这里了
再贴个图吧

上次说 了hook和窗口类的原理,有了hook,我们可以截取所有消息,有了窗口类,我们可以识别窗口类型,不同类型的窗口给予不同处理。这样,我们要在钩子函 数里面识别不同的窗口和不同的消息,有大量的分派工作,更要命的是,光区分窗口类还不够,同类型的不同窗口经常需要不同的处理,例如两个button窗 口,大小不同,文字不同,是否有鼠标按下不同...... 这些状态有些是可以从button窗口读到的,例如大小和文字,而有些则读不到,比如是否有鼠标按下,对这些读不到的状态,我们必须自己记录,例如在收到 WM_LBUTTONDOWN消息时记下按钮被按下了。也就是说,对于每个窗口,我们还需要记录一些与其相对应的数据,以便在收到WM_PAINT消息时 做不同处理。把所有这些逻辑写在钩子函数里显然太麻烦了,即使能写出来也没法维护,我们需要一个好的设计。
根据面向对象的思想,我们需要为每种窗 口类型写一个类,并为每个窗口生成一个对应类的实例,由这些实例来处理窗口消息,并记录必要的窗口状态数据。这样,处理窗口消息的任务就交给这些对象了, 那么,怎么把消息传递给这些对象呢,用钩子函数转发是一种方案,不过我们这里采用了另一种:SubclassWindow,关于SubclassWindow的原理,就不多讲了,可以参看MSDN,其实就是替换一个窗口过程函数。ATL提供了现成的支持,用起来还是很方便的,替代的窗口过程函数不用全部自己写,而可以用消息映射宏生成。
现在我们用SubclassWindow的方式可以直接把我们的对象链接到窗口的消息链中,这好像有点和钩子函数的功能重复了,因为钩子函数本来就是用来截获消息的。现在SubclassWindow以后,窗口的消息已经可以被截获了,那还要钩子函数干什么呢。
答案是:钩子函数用来执行SubclassWindow操作。 原因有两个,第一,我们要做的是一个skin plugin,我们希望使用者调用一个函数就可以改变整个界面风格,而不是为每个窗口调用SubclassWindow函数;第二,有些窗口的创建根本不 是在代码里控制的,例如菜单窗口,除了使用钩子函数,我们甚至不能获得菜单窗口的句柄。所以,我们必须使用钩子函数,但在钩子函数中,我们只处理一个消 息:WM_CREATE,在任何一个可识别窗口创建时,生成一个对于的对象实例,并用SubclassWindow挂接这个实例到目标窗口,剩下的事情让 这个对象实例去完成。

粗略的设计已经有了,总结一下:
1、为每种可识别的窗口类编写类,实现必要的消息处理和状态保存;
2、用钩子函数截取WM_CREATE消息,并创建对应的类实例;
3、通过SubclassWindow操作把生成的类实例挂接到目标窗口,完成消息处理和状态保存的工作;

今天有点空了,继续写。上次我 们已经得出了基本的设计,由此确定了每种窗口必须有一个类来与之对应,这里所说的窗口种类是按照窗口的windows class名称来区分的,class名称相同的就认为是一种窗口。这种分类方法和我们看到的窗口种类可能有一些差异,例如,普通按钮,单选按钮和复选框的 类名都是“Button”,对于这种情况,我们仍然用一个类来对应这些窗口,而在类内部区分对待这些不同的窗口。
这样,我们要为每种需要改变外观的控件窗口编写一个类,根据面向对象的思想,我们很自然的想到提取出它们的公共基类,这就是CWidgetHookBase,所有控件窗口处理类的公共基类,实际上是一个C++接口,因为它只包含一个纯虚函数,下面是它的定义:

///

/// Abstract base class for widget hook

class CWidgetHookBase

{

public:

     virtual void Install(HWND hWidget) = 0; //implemented in CWidgetHook

    

};

 


这 个接口中唯一的Install函数用来实现把对象链接到窗口的功能,也就是SubclassWindow,这会在继承类实现,后面我们再说怎么实现它。今 天要讲的实际上是控件类工厂,也就是CWidgetFactory及其继承类。下面是CWidgetFactory的完整声明和实现:

/

/// Abstract factory class for widget hooks. create hook instances

class CWidgetFactory

{

protected:

     static CWidgetFactory* m_pInstance;

public:

     // initialize the instance

     CWidgetFactory()

     {

         ATLASSERT(m_pInstance==NULL);

         m_pInstance = this;

     }

     // Get the singleton instance

     static CWidgetFactory* Instance()

     {

         return m_pInstance;

     }

     virtual CWidgetHookBase*    CreateWidget(LPCTSTR szClass) = 0;

};

 

CWidgetFactory* CWidgetFactory::m_pInstance = NULL;


CWidgetFactory使用了两个设计模式,Singleton模式和Abstract Factory模式,实际上还包括Factory Method模式。
首 先看抽象工厂模式,我们希望控件工厂根据窗口class的名字创建出不同的控件窗口消息处理类。对于模拟Mac的系统,这些控件窗口消息处理类包括 CMacButton, CMacComboBox, CMacTrackBar等;而对于模拟KDE的系统,则是CKDEButton, CKDEComboBox等。这样,我们就可以定义两个CWidgetFactory的继承类,分别叫CMacFactory和CKDEFactory, 分别产生这两个系列的对象。CWidgetFactory::CreateWidget就是用来产生这些对象的方法,它是个纯虚函数,必须在继承类中实 现。CreateWidget接受窗口class的名字为参数,返回CWidgetHookBase指针,也就是所有控件类的基类。这样,每个对象工厂负 责产生一系列对象,但对于一个应用程序来说,应该只有一种风格,也就是说,只能有一个工厂的实例,单件模式来了
这里使用了简化版的Singleton模式,需要声明一个继承类的实例,然后通过CWidgetFactory的静态函数Instance得到这个唯一实例。这里没有控制不能生成第二个实例,不过这不是大问题。
现在来看Factory的一个实现,CMacFactory,完整的代码如下:

class CMacFactory : public CWidgetFactory

{

public:

     virtual CWidgetHookBase*    CreateWidget(LPCTSTR szClass)

     {

         if (lstrcmpi(szClass, "Button") == 0 )

              return new CMacButton;

         else if (lstrcmpi(szClass, "#32770") == 0) //dialog

              return new CMacDialog;

         else if (lstrcmpi(szClass, "ListBox") == 0)

              return new CMacListBox;

         else if (lstrcmpi(szClass, WC_TABCONTROL) == 0)

              return new CMacTabCtrl;

         else if (lstrcmpi(szClass, "#32768") == 0) //menu

              return new CMacMenu;

         else if (lstrcmpi(szClass, "ComboBox") == 0) //combobox

              return new CMacCombo;

         else if (lstrcmpi(szClass, TRACKBAR_CLASS) == 0) //trackbar

              return new CMacTrackBar;

         return NULL;

     }

 

};


再看看消息钩子的代码,都很简单吧

LRESULT CALLBACK CMacSkin::HookProc(int nCode, WPARAM wParam, LPARAM lParam)

{

     CWPSTRUCT cwps;

    

     if( nCode == HC_ACTION )

     {

         CopyMemory(&cwps, (LPVOID)lParam, sizeof(CWPSTRUCT));

        

         switch(cwps.message)

         {

         case WM_CREATE:

              {

                   CHAR szClass[MAX_PATH];

                   GetClassName(cwps.hwnd, szClass, MAX_PATH);

 

                   CWidgetHookBase*   pWidget=NULL;

                  

                   pWidget = CWidgetFactory::Instance()->CreateWidget(szClass);

                   if (pWidget)

                       pWidget->Install(cwps.hwnd);

              }

              break;

         }

     }

    

     return CallNextHookEx((HHOOK)CMacSkin::m_hHook, nCode, wParam, lParam);

}


不管对于CMacFactory还是其他的工厂实现,以及不同的控件类系列,钩子函数的实现都是一样的。CWidgetFactory继承类的实现也非常相似,只是替换一些类名而已。
今天把工厂类和钩子都讲完了,可能不是很清楚,那也只能这样了,其实看代码最清楚了。下次讲怎么实现控件吧,包括如何利用ATL/WTL的基础架构,那是我最喜欢的部分了

上篇, 控件类的接口有了:CWidgetHookBase,产生控件对象的工厂也有了,下面就该实现控件类了。在上篇定义控件基类的时候,我们只定义了一个抽象 函数Install,而没有任何其他代码,那么,所有的实现代码都交给各个控件类去实现吗?不是的,这些控件类还有许多公共代码可以在基类实现,但是,我 们选择不在CWidgetHookBase中加入这些代码,而是再加入一个中间类:CWidgetHook。为什么不把这两个基类合成一个类呢?其实,最 初的设计是只有一个基类的,就是CWidgetHook,而我们又希望在继承类中使用WTL对Windows控件的包装类,这样,根据ATL/WTL的架 构,CWidgetHook就必须是一个模板类,而模板类是不能作为基类指针的,因为模板是类型不定的,而我们的抽象工厂模式要求一个基类指针,所以我们 又提取出CWidgetHookBase这样的纯虚的接口类。结果就是如下图所示的结构:

下面是CWidgetHook模板类的声明:

///

/// Base class for all widget hook

/// Parameters:

/// T

///      Derived class

/// TBase

///      Widget window wrapper, use WTL wrappers for convenient

template <class T, class TBase = CWindow>

class CWidgetHook : public CWidgetHookBase, public CWindowImpl<T, TBase>

这里用了多重继承,也是ATL里常用的,第一个父类是我们前面定义的CWidgetHookBase接口,我们需要这个接口来实现抽象工厂设计模式。第二个父类是CWindowImp,这是ATL定义的,是所有窗口类的高层父类(虽然还不是顶层)。
CWindowImp 接收两个模板参数,同时也是CWidgetHook的模板参数。第一个模板参数是继承类,这也是ATL中常用的技巧,这个技巧使得我们可以在父类中知道继 承类的类型,于是,把this指针强制转换成继承类的类型,就可以调用继承类的方法,这种方式实现了类似于虚函数的多态,却不需要付出虚函数的性能代价。 这也算把C++模板运用到及至了吧,好像只有在ATL中有这种用法,STL中有很多其他的看起来近似古怪的技巧,这些其实正是C++的魅力所在。后来的语 言,如java,C#虽然也在各方面都有新的进步,但比起C++,真正值得人兴奋的地方还真很难找出来。
扯远了,继续说我们的第二个模板参数。 追踪ATL的代码,可以看到其实TBase参数最终是作为基类的,通过模板参数改变基类,这也是模板之于OO不同的地方。TBase有一个默认值 CWindow,可以在这里传入其他类,但必须是CWindow的继承类,最有价值的参数当然是WTL窗口包装类,这样,在实现控件消息处理类的时候,就 可以使用WTL包装类提供的函数,而不需要只依赖windows API了,确实可以带来不少帮助。
才讲完CWidgetHook的声明,真够罗嗦的。下面来看CWidgetHook的定义和实现,首先看几个函数声明:

     void Initialize() {}; //instance initialize

     void Finalize() {};    //instance finalize

     static void InitializeClass() {}; //class initialize

     static void FinalizeClass() {};  //class finalize


上面四个函数都包含了空的实现,在继承类中可以选择的重载它们。重载非虚函数,利用上面讲到的模板技巧实现类似虚函数的多态,这就是模板给我们带来的新概念。Initialize在每个实例生成时被调用,Finalize在实例销毁前被调用;静态函数InitializeClass在类的第一个实例生成时调用,FinalizeClass在类的最后一个实例销毁时调用。
为 什么要有两个静态函数呢,因为一个类代表同一种窗口,这些窗口会使用同样的资源,例如checkbox,需要几张不同状态的图片,而这些图片对于每个 checkbox来说都是一样的,如果为每个实例保存这样一份资源,就有点浪费内存了。对于一个Skin插件来说,效率还是很重要的,所以我们选择用静态 变量保持这些图片,并且在第一个checkbox生成的时候载入这些资源,后续生成的其他checkbox可以重用这些资源,然后在最后一个 checkbox消失的时候释放这些资源,这样,内存的使用量被优化到最小。
这两个静态函数的调用在构造函数和析构函数中,另外还有一个实例计数值m_lRef配合,这里就不多说了。下面还剩下OnFinalMessage和Install函数没讲。
OnFinalMessage 比较简单,调用Fianlize并删除自己,由于是CWindowImp继承类,OnFinalMessage函数会在窗口销毁的时候被自动调用,这样, 就保证了实例会自己释放,不会造成内存泄漏。另外值得一提的是:在调用Finalize等上面提到的四个函数时,都是先把this指针转换成继承类T的指 针pT,然后在pT指针上调用。这样才能实现类似虚函数的多态,这种方式据说比使用虚函数的方式效率高,不过我这样写倒不是因为要刻意提高效率,只不过, That's the ATL way
最 后来看Install函数,这是在CWidgetHookBase中定义的纯虚函数,在CWidgetHook模板类中,这个函数得以实现。 Install函数的主要功能是调用SubclassWindow,从而获得对窗口消息的控制。另外,还实现了控制消息反射和初始化实例的操作。下面是它 的代码:

     virtual void Install(HWND hWidget)

     {

         ATLASSERT(::IsWindow(hWidget));

 

         SubclassWindow(hWidget);

        

         //if it is a child window, install a reflector for its parent

         //so that the parent will reflect messages back to me

         if ( (::GetWindowLong(hWidget, GWL_STYLE) & WS_CHILD) == WS_CHILD)

         {

              HWND hWndParent = ::GetParent(hWidget);

              ATLASSERT(::IsWindow(hWndParent));

              //the WM_GETREFLECTOR get the installed reflector, if exists

              CReflectHook* pReflector =

                   (CReflectHook*)::SendMessage(hWndParent, WM_GETREFLECTOR, 0, 0);

              if (!pReflector)

                   new CReflectHook(hWndParent);

         }

 

         T* pT = static_cast<T*>(this);

         pT->Initialize();

     }

中 间的一大块代码都是关于消息反射的,留到下次在讲,今天写的够长的了,最后讲一下Initialize吧。其实这里可以看到一个设计模式:模板方法。 Install函数是模板方法,它调用的Initialize方法则要在继承类中重载。那么,Initialize方法是实例的初始化函数,为什么不放在 构造函数里呢?因为每个实例的初始化可能不太一样,要根据被挂钩的窗口的状态决定,所以,必须等到SubclassWindow被调用之后,才调用 Initialize方法,在继承类的Initialize实现中,可以通过m_hWnd直接获得窗口句柄,调用API或者WTL包装类方法检查窗口状 态,并执行必要的实例初始化代码。
好了,今天差不多了,剩点尾巴下次讲

有过去一个周末了,昨天去看跳水比赛,现场的气氛还是不错的。可惜田亮没有来,否则,光看看观众席的fans也是一种享受啊
废话结束,进入正题,今天讲点以前没说清楚的内容。上次提 到了消息反射,但没有深入,这个概念是这样的,许多窗口控件会向父窗口发送一些消息,比如WM_COMMAND消息和WM_NOTIFY消息,通知父窗口 一些事件。因为是发给父窗口的,所以控件窗口的过程函数不能捕捉到这些消息。但是,经常我们希望在控件窗口对象中处理这些消息,这样使控件类更加独立。为 了实现这个目的,MFC和ATL都提供了消息反射的机制,就是让父窗口在收到这类消息的时候,把它们再发还给控件窗口,这就是消息反射。我们要实现 Skin插件,也需要在控件窗口类中收到这些消息,但是,我们不能依赖ATL或者MFC的反射,因为我们希望Skin插件可以被不同的宿主程序使用,而不 是局限于ATL和MFC。其他程序可能没有消息反射机制,或者使用了不同的消息反射机制。所以,我们实现了自己的消息反射机制。
CReflectHook 类就是用来完成消息反射的,其构造函数接受父窗口的句柄作为参数,然后调用SubclassWindow把对象实例链接到窗口上去,这和控件的实现类似。 ProcessWindowMessage是个虚函数,它的定义可以追述到ATL窗口类的最底层,CReflectHook:: ProcessWindowMessage实现反射功能,把收到的需要反射的消息发送回控件窗口。但也不是简单的原样返回,而是包装成另一个 WM_REFLECT消息,以免和其他消息冲突。当反射消息发会给控件窗口时,控件窗口利用下面三个宏解开WM_REFLECT,并得到原来的消息:
LF_REFLECTED_NOTIFY_CODE_HANDLER
LF_REFLECTED_COMMAND_CODE_HANDLER
LF_REFLECTED_MESSAGE_HANDLER
这 几个宏的含义不多说了,熟悉WTL的同学很容易找到答案。另外值得一提的时WM_GETREFLECTOR消息,这个自定义消息可以用来向父窗口查询于其 相关联的CReflectHook实例,以避免重复安装反射钩子,因为CReflectHook::ProcessWindowMessage为这个消息 返回了this指针。CWidgetHook::Install使用了WM_GETREFLECTOR消息。

值得讲的就这么多了吧,最后 说一下怎么写CWidgetHook继承类吧(即控件类),这也是直接影响最终效果的。WTL定义了许多通用控件包装类,把这些类作为 CWidgetHook的第二个模板参数可以是后续工作大大简化,当然,如果没有对应的包装类,也可以接收默认参数。
因为借助了ATL/WTL的基础架构,控件类的编写和写一个ATL窗口类非常相似,可以使用ATL/WTL消息映射宏,当然,这些宏需要手工输入,而不象MFC一样提供了Wizard。另外,控件类还可以选择的重载CWidgetHook定义的4个初始化和清理函数,参考Skin技术实现框架(五)。附带的例子中提供了一个Mac按钮类的实现,可以参考,这个按钮例子算是比较复杂的,因为Windows窗口类名为“Button”的窗口实际上包括普通按钮、单选钮和复选框,其他的许多控件实现起来比button容易,当然也有一些比较麻烦的。

关于这个skin框架,基本上应该都讲清楚了,不过肯定比较乱,也许以后有时间整理吧。这里使用了很多WTL的东西,可能熟悉的人并不多,而且最终没有得到微软官方支持,所以要说有多少价值,也说不上,只是喜欢的朋友可以玩一下。我现在也不怎么关注C++的东西了,没办法,新技术发展太快,不得不跟上啊。随便写一点以前的积累,和同学们共勉。

 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目前这方面的软件很多,但大部分都是收费的,不收费大部分又换的不全,对于一个学生来说花钱买是有些奢侈了,所以我一直就想做一个换肤软件提供给学生,让他们做课程设计或毕业设计时能轻易给自己软件美化界面。 但是一直苦于时间有限。工作太忙有时只能在周末或晚上写上两行代码。现在终于成形了本打算开源,但是有些地方还不完善(现只支持VC MFC, Windows Type: Dialog, SDI),所以现在只讲下原理,提供部分源码供感兴趣的人研究。现在发出来与大家共享。 现在商业的换肤软件大部分都是采用的Hook技术(呵呵,猜的,也许采用的更高深的技术)。Hook窗体消息,对窗体消息进行截获最终换成自已的处理方式。所以本人写的SkinMaster也是采用了同样的技术原理。说很简单但做起来有些困难。下面是我做Skin时遇到的问题及处理方式。 1.对于Windows基本控件进行Hook则可完成绘制。 2.对于菜单会制则有些麻烦,程序运行时窗体菜单WM_MEASUREITEM只运行一次,所以会出现在动态换另一套皮肤时菜单项大小不会跟据皮肤改变,解决方法是所有菜单你要动态生成。 3.主窗体的绘制,没啥太深技术就是要处理大量的消息。 4.滚动条的绘制,滚动条全靠Hook消息就没办法完成了,这个东西微软做的不像基本控件那样工作,还要对滚动条的API进行Hook。 先写这些,有时间我会把更详细的方法给大家写出来。下面程序中TestSkin程序提供源码,并完成了按钮等控件的换肤。
第1部分 基础知识   第1章 起步   1.1 Windows环境   1.1.1 Windows简史   1.1.2 Windows的方方面面   1.1.3 动态链接   1.2 Windows编程选项   1.2.1 API及内存管理模式   1.2.2 语言选择   1.2.3 编程环境   1.2.4 API文档   1.3 你的第一个Windows程序   1.3.1 字符模式   1.3.2 Windows对应程序   1.3.3 头文件   1.3.4 程序入口   1.3.5 MessageBox函数   1.3.6 编译、链接及运行   第2章Unicode简介   2.1 字符集简史   2.1.1 美国标准   2.1.2 美国以外的世界   2.1.3 扩展ASCII   2.1.4 双字节字符集   2.1.5 Unicode的解救方案   2.2 宽字符和c语言   2.2.1 char数据类型   2.2.2 更宽的字符   2.2.3 宽字符库函数   2.2.4 维护一个源代码文件   2.3 宽字符和Windows   2.3.1 Windows头文件的类型   2.3.2 Windows函数调用   2.3.3 Windows的字符串函数   2.3.4 在Windows中使用printf   2.3.5 格式化的消息框   2.3.6 国际化之于本书   第3章 窗口与消息   3.1 窗口的创建   3.1.1 系统结构概述   3.1.2 HELLOWIN程序   3.1.3 通盘考虑   3.1.4 窗口类的注册   3.1.5 窗口的创建   3.1.6 窗口的显示   3.1.7 消息循环   3.1.8 窗口过程   3.1.9 消息的处理   3.1.10 声音文件的播放   3.1.11 WM_PAINT消息   3.1.12 WM_DESTROY消息   3.2 Windows编程中的若干难点   3.2.1 究竟是谁调用谁   3.2.2 队列消息和非队列消息   3.2.3 速战速决   第4章 文本输出   4.1 绘制和重绘   4.1.1 WM_PAINT消息   4.1.2 有效矩形和无效矩形   4.2 GDI简介   4.2.1 设备环境   4.2.2 获取设备环境句柄:方法一   4.2.3 绘制信息结构   4.2.4 获取设备环境句柄:方法二   4.2.5 TEXTOUT函数详解   4.2.6 系统字体   4.2.7 字符大小   4.2.8 文本尺寸的度量   4.2.9 文本的格式化   4.2.10 综合使用   4.2.11 SYSMETSl.C窗口过程   4.2.12 空间不够   4.2.13 客户区的尺寸   4.3 滚动条   4.3.1 滚动条的范围和位置   4.3.2 滚动条消息   4.3.3 加入滚动条的SYSMET   4.3.4 程序的绘制代码的结构   4.4 效果更好的滚动   4.4.1 滚动条信息函数   4.4.2 最远可以卷动到哪里?   4.4.3 新的SYSMETS   4.4.4 可我不想用鼠标   第5章 绘图基础   5.1 GDI的结构   5.1.1 GDI原理   5.1.2 GDI函数调用   5.1.3 GDI的基本图形   5.1.4 其他   5.2 设备环境   5.2.1 获取设备环境句柄   5.2.2 获取设备环境的信息   5.2.3 DEVCAPSl程序   5.2.4 设备的尺寸   5.2.5 色彩ABC   5.2.6 设备环境属性   5.2.7 保存设备环境   5.3 点和线的绘制   5.3.1 设定像素   5.3.2 直线   5.3.3 边框绘制函数   5.3.4 贝塞尔样条曲线   5.3.5 使用现有画笔   5.3.6 创建、选择和删除画笔   5.3.7 填充空隙   5.3.8 绘图模式   5.4 绘制填充区域   5.4.1 Polygon函数和多边形填充模式   5.4.2 用画刷填充内部   5.5 GDI映射模式   5.5.1 设备坐标和逻辑坐标   5.5.2 设备坐标系统   5.5.3 视口和窗口   5.5.4 使用MMTEXT   5.5.5 度量映射模式   5.5.6 自定义的映射模式   5.5.7 WHATSIZE程序   5.6 矩形、区域和剪裁   5.6.1 处理矩形   5.6.2 随机矩形   5.6.3 建立和绘制区域   5.6.4 矩形与区域的剪裁   5.6.5 CLOVER程序   第6章 键盘   6.1 键盘基础   6.1.1 忽略键盘   6.1.2 谁获得了焦点?   6.1.3 队列和同步   6.1.4 击键和字符   6.2 击键消息   6.2.1 系统键击和非系统键击   6.2.2 虚拟键代码   6.2.3 1param信息   6.2.4 转义状态   6.2.5 使用击键消息   6.2.6 为SYSMETS加上键盘处理功能   6.3 字符消息   6.3.1 四类字符消息   6.3.2 消息排序   6.3.3 控制字符的处理   6.3.4 死字符消息   6.4 键盘消息和字符集   6.4.1 KEYVIEW1程序   6.4.2 非英语键盘问题   6.4.3 字符集和字体   6.4.4 Unicode解决方案   6.4.5 TrueType字体和大字体   6.5 插入符号(不是光标)   6.5.1 一些关于插入符号的函数   6.5.2 TYPER程序   第7章 鼠标   7.1 鼠标的基础知识   7.1.1 一些基本术语   7.1.2 鼠标的复数形式是什么?   7.2 客户区鼠标消息   7.2.1 简单的鼠标处理示例   7.2.2 处理Shift键   7.2.3 鼠标双击   7.3 非客户区鼠标消息   7.3.1 击中测试消息   7.3.2 消息引发消息   7.4 程序中的击中测试   7.4.1 一个假想的例子   7.4.2 一个简单的程序   7.4.3 使用键盘模仿鼠标操作   7.4.4 在CHECKER中增加键盘接口   7.4.5 在击中测试中使用子窗口   7.4.6 CHECKER程序中的子窗口   7.4.7 子窗口和键盘   7.5 捕获鼠标   7.5.1 设计一个矩形   7.5.2 捕获的解决方案   7.5.3 BLOKOUT2程序   7.6 鼠标的滚轮   第8章 计时器   8.1 计时器的基本知识   8.1.1 系统和计时器   8.1.2 计时器消息不是异步的   8.2 使用计时器的三种方法   8.2.1 方法一   8.2.2 方法二   8.2.3 方法三   8.3 使用计时器作为时钟   8.3.1 数字时钟   8.3.2 获取当前时间   8.3.3 显示数字和冒号   8.3.4 考虑国际化   8.3.5 模拟时钟   8.4 在状态报告上使用计时器   第9章 子窗口控件   9.1 按钮类   9.1.1 创建子窗口   9.1.2 子窗口传递信息给父窗口   9.1.3 父窗口传递信息给子窗口   9.1.4 按钮   9.1.5 复选框   9.1.6 单选按钮   9.1.7 组合框   9.1.8 改变按钮文本   9.1.9 可见的按钮和启用的按钮   9.1.10 按钮和输入焦点   9.2 控件和颜色   9.2.1 系统颜色   9.2.2 按钮的颜色   9.2.3 WMCTLCOLORBTN消息   9.2.4 自绘按钮   9.3 静态类   9.4 滚动条类   9.4.1 COLORS1程序   9.4.2 自动键盘接口   9.4.3 窗口子类   9.4.4 背景着色   9.4.5 给滚动条和静态文本着色   9.5 编辑类   9.5.1 编辑类的样式   9.5.2 编辑控件的通知消息   9.5.3 使用编辑控件   9.5.4 传递给编辑控件的消息   9.6 列表框类   9.6.1 列表框的样式   9.6.2 向列表框中添加字符串   9.6.3 项目的选择和提取   9.6.4 接收来自列表框的消息   9.6.5 简单的列表框程序   9.6.6 列出文件   9.6.7 Windows的HEAD程序   第10章 菜单和其他资源   10.1 图标、鼠标指针、字符串和自定义资源   10.1.1 向程序添加图标   10.1.2 获得图标的句柄   10.1.3 在应用程序中使用图标   10.1.4 使用自定义鼠标指针   10.1.5 字符串资源   10.1.6 自定义资源   10.2 菜单   10.2.1 和菜单有关的概念   10.2.2 菜单结构   10.2.3 定义菜单   10.2.4 在程序中引用菜单   10.2.5 菜单和消息   10.2.6 范例程序   10.2.7 菜单设计中的规范   10.2.8 定义菜单的繁琐方式   10.2.9 浮动弹出菜单   10.2.1 0使用系统菜单   10.2.1 1改变菜单   10.2.1 2其他菜单命令   10.2.1 3菜单的另类用法   10.3 键盘加速键   10.3.1 为什么你应该使用键盘加速键   10.3.2 指定加速键的一些规则   10.3.3 加速键表   10.3.4 加载加速键表   10.3.5 翻译按键   10.3.6 接收加速键消息   10.3.7 带有菜单和加速键的POPPAD程序   10.3.8 启用菜单项   10.3.9 处理菜单项   第11章 对话框   11.1 模态对话框   11.1.1 创建一个About对话框   11.1.2 对话框及其模板   11.1.3 对话框过程   11.1.4 激活对话框   11.1.5 主题变换   11.1.6 更复杂的对话框   11.1.7 对话框控件的应用   11.1.8 OK和Cancel按钮   11.1.9 避免全局变量   11.1.1 0Tab停靠和选项组   11.1.1 1在对话框上绘图   11.1.1 2关于对话框的其他函数.   11.1.1 3定义程序自己的控件   11.2 非模态对话框   11.2.1 模态与非模态对话框的区别   11.2.2 新的COLORS程序   11.2.3 HEXCALC:窗口还是对话框?   11.3 公用对话框   11.3.1 完善POPPAD   11.3.2 Unicode文件的读/写操作   11.3.3 改变字体   11.3.4 查找和替换   11.3.5 只调用一个函数Windows程序   ……   第Ⅱ部分 关于图的那些事儿   第Ⅲ部分 高级主题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值