激烈振动

Visit My MSN Space

导航

SkinX, an infrastructure of skin package

Introduction

I'm not sure what kind of technology they used in those commercial skin components, but this artical will give you one implementation of such skin framework. The code is part of an incompleted project. The project was finally given up because of some business reason and never get used. The original purpose of that project is to create different COM objects with same interface, so that the host application can pick one of those objects and get different look and feel.

The attatched source code includes a small subset of an Mac Os X look&feel object and a demo host application. The application simply create the skin object and call InstallHook method, then all the buttons in the application get a Mac look&feel. I removed other code from the COM object implementation to make it simple. Anyway, this article is aim to explain the framework, not to give a ready-to-use project.

Skin Theory

A skin component needs to modify the default look&feel of the application windows, like buttons, comboboxes, etc. To do this, a custom windows procedure must be pluged in to the window class. The Windows hook functionality allow us to do that. Calling SetWindowsHookEx with WH_CALLWNDPROC as hook ID allow us to monitor all the Windows messages before it is sent to the destination window procedure, so we can kidnap certain messages and process them, and then pass it through to the original window procedure, or even eat some messages.

After the hook is installed, all windows message will go into our hook procedure. But choosing which message we must process is not an easy job. There are thousands of messages related with different kinds of window. For a single message, we must process it in different way for different kind of windows.

There are a lot to talk about hook and message processing, but let's just skip those nonsense and give the solution directly. We define different class for each different kind of window. For example, we define a CMacButton class to wrap the window procedure, which will give a button window look&feel of Mac OS. And in our hook procedure, we just process one message: WM_CREATE. Then we check to see if it is a button window which is going to be created. If it is, we create a CMacButton instance, and use SubclassWindow to hook our window procedure to the destination window. Then, the CMacButton instance will take over the responsibility of processing window messages.

To conclude, we use SetWindowsHookEx to kidnap WM_CREATE message. For each window that we hope to change its look&fell, we create an object, and use SubclassWindow to connect the object with the window.

Infrastructure

Ok, whether you understand the above or not, I'm finished with the theory. But there's a long way from theory to executable code. The framework, or infrastructure is the key part of this project. The SkinX framework borrow a lot form ATL/WTL library. Let's take a look at the UML modal first:

The Widget Classes

As we described above, we must define different window class for each widget, such as CMacButton, CMacEdit. These class process various messages for different kind of window. All these window classes, we called widget classes, are derived from a common base class: CWidgetHook, and CWidgetHook is derived from CWidgetHookBase. Let's dig into these classes.

CWidgetHookBase just define the interface, which only include one method: Install. The framework call Install to hook the instance to the destination window. Install is implemented in CWidgetHook class. CWidgetHook also define some methods which should be override in derived class, they are:

 ////////////////////////////////////////////////////////////////////
 // overrides, we don't need virtual member since we use the ATL way
 void Initialize() {}; //instance initialize
 void Finalize() {}; //instance finalize
 static void InitializeClass() {}; //class initialize
 static void FinalizeClass() {}; //class finalize

The first two methods get called every time a single instance is created or destroyed. The last two methods get called when the first instance of a class is created or when the last instance of a class destroyed. The reason of define the last two methods is that for each class, there are some common resources needed. For example, all checkboxes need some bitmaps. To maintain a copy of such resources for each instance is not efficient. So it would be better to use a static member to keep these resources and use static member functions to initialize them and release them.

CWidgetHook also have a static member: m_lRef, which is a refrence counter. So that the the class know how many instances exist. When the last instance is destroyed, FinalizeClass will be called to clean static members, so that memory footprint is reduced. CWidgitHook:OnFinalMessage delete the instance itself, so we don't need to worry about cleanup.

CWidgetHook is a C++ template, one template parameter is the derived class. This concept is borrowed from ATL. The other parameter is a CWindow compatible class, which could be a WTL wrapper class, so that we can use WTL wrapper method in derived class. Such design make the implementation of a window procedure less pain. Actually, writting a CWidgetHook derived class is as easy as write a WTL window class. ATL windowing and message map macros help a lot in writting and maintaining code.

The Hardest thing in writting a skin package is to write these CWidgetHook derived classes. For most such classes, WM_PAINT message must be processed to give the window another look. Some other message should also be catched so that you know the state of the window. The attached demo project include a implementation of CMacButton class, which actually implement the Mac look&feel for buttons, checkboxes and radio, since all these three kinds widgets have a same window class name: Button. Check the code yourself for how to write a widget class.

The Reflector Hook

The reason a reflector exists is that lots of widgit window messages are just sent to their parent window, not themselves. A reflector make the parent window send back these messages to the original widget window. ATL and MFC both have reflect support. Here we use our own implementation of reflector to avoid conflict.

The Widget Factory

when all the widget classes are ready, we need a way to map these classes with the widget window, and install them to the destination widget window. CWidgetFactory implement the abstract factory pattern. it uses the widget window class name to create corresponding instance. CreateWidget method takes the class name of the destination window, and return a CWidgetHookBase interface. CreateWidget method is abstract and must be implemented in derived class.

The Hook Procedure

With the above infrastructure, the Hook procedure become very simple and clear. It just checks the destination window class name, asks Widget factory for a CWidgetHookBase interface, and calls the Install method on the interface to install the class.

How to extend

The framework is designed for easy extending. To write a skin package, first you need to derive a serious of classes from CWidgetHook, implement them to override the default behaviour to change the look&feel of the destination window. Then you create you Widget Factory by derive a class from CWidgetFactory, implement the CreateWidget method to create instance by window class. Then you are almost done, the COM object code and the Hook procedure are the same as in the demo project.

That's it

Well, that's almost all about this framwork. It's fully extensible, and take efficiency as a main concern. Take some time to understand this framework and begin to write some creative code, a powerful skin package could be created.

That's what I contribute here. It's not perfect. If you find something that should be improved, tell me please. If you find it useful, then use it. If you create something wonderful with this, please tell me. I won't claim anything about this, but it will make me happy. And if I can get involved in some wonderful project, that will be exciting.

posted on 2004-05-13 15:28  vibration  阅读(2258)  评论(4编辑  收藏  举报