激烈振动

Visit My MSN Space

导航

Skin技术实现框架(五)

上篇,控件类的接口有了: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包装类方法检查窗口状态,并执行必要的实例初始化代码。
好了,今天差不多了,剩点尾巴下次讲

posted on 2004-05-21 16:00  vibration  阅读(4000)  评论(0编辑  收藏  举报