Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SkinX, A framework of a skin plug-in package

0.00/5 (No votes)
16 May 2004 1  
Introduces the framework of a skin plug-in implementation.

Sample Image - SkinX.jpg

Introduction

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

The attached source code includes a small subset of a Mac OS X look & feel object and a demo host application. The application simply creates the skin object and calls InstallHook method, then all the buttons in the application gets a Mac look & feel. I removed other code from the COM object implementation to make it simple. Anyway, this article is aimed 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, combo boxes, etc. To do this, a custom window procedure must be plugged into the window class. The Windows hook functionality allows us to do that. Calling SetWindowsHookEx with WH_CALLWNDPROC as hook ID allows us to monitor all the Windows messages before they are 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 messages 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 windows. For a single message, we must process it in different ways for different kinds of windows.

There is a lot to talk about hooks and message processing, but let's just skip those nonsense and give the solution directly. We define different classes for each different kind of window. For example, we define a CMacButton class to wrap the window procedure, which will give a button window the 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 & feel, 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 borrows 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 classes for each widget, such as CMacButton, CMacEdit. These classes process various messages for different kinds of windows. 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 defines the interface, which only includes one method: Install. The framework calls Install to hook the instance to the destination window. Install is implemented in CWidgetHook class. CWidgetHook also defines some methods which should be overridden in the 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 is destroyed. The reason of defining 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 has a static member: m_lRef, which is a reference counter, so that the class knows 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 deletes the instance itself, so we don't need to worry about cleanup.

CWidgetHook is a C++ template class, 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 makes the implementation of a window procedure less pain. Actually, writing a CWidgetHook derived class is as easy as writing a WTL window class. ATL windowing and message map macros help a lot in writing and maintaining code.

However, the hardest thing in writing a skin package is still 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 caught so that you know the state of the window. The attached demo project includes an implementation of CMacButton class, which actually implements the Mac look & feel for buttons, checkboxes and radio, since all these three kinds of widgets have the 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 widget window messages are just sent to their parent window, not themselves. A reflector makes the parent window send back these messages to the original widget window. ATL and MFC both have reflector 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 implements the abstract factory pattern. It uses the widget window class name to create a corresponding instance. CreateWidget method takes the class name of the destination window, and returns a CWidgetHookBase interface. CreateWidget method is abstract and must be implemented in the derived class.

The Hook Procedure

With the above infrastructure, the Hook procedure becomes 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 series of classes from CWidgetHook, implement them to override the default behavior to change the look & feel of the destination window. Then you create your Widget Factory by deriving 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 framework. It's fully extensible, and takes 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here