Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

A Lightweight Window Wrapper


August 2000/A Lightweight Window Wrapper


Recently, I faced the task of writing a small Windows application. I didn't need the bulkiness of MFC, or the complexity of WTL. The application consisted of a status window and a few dialogues. It seemed natural to code it using only the Windows API — no fancy frameworks required. I pulled out a few books to refresh my memory on window creation, and found myself staring at C code, without an object to be found. Programming half of the application without objects would have crippled my design. I needed an inheritable window wrapper.

In Windows, there are two steps to creating a window. First, you call the Windows function RegisterClassEx, which inputs a structure that specifies a class name for the window, as well as other attributes. (Don't confuse a Windows "class" with a C++ class; they're not the same thing.) RegisterClassEx associates the class name with a callback procedure, which is also specified in the structure. Next, you create the window by calling CreateWindowEx with the registered class name and more specific properties, such as the title and size.

The problem in creating a wrapper is the callback procedure, the function that Windows calls to process messages. If it is a member function, this callback procedure must be static, because Windows needs its address when its class name is registered. Unfortunately, static member functions cannot access other non-static members without a this pointer, and most importantly, they cannot be virtual.

The solution to this problem is to somehow provide the callback procedure with a pointer to the object to which it belongs. This must be arranged for during the creation process, since there is no window before creation, and if it is left until later, clients will miss dozens of important messages sent during creation. Luckily, Windows allows the caller of CreateWindowEx to pass a void pointer as the last parameter. This pointer is included in the CREATESTRUCT structure that accompanies a WM_CREATE or WM_NCCREATE message — a couple of the earliest messages Windows sends to a new window. Of the two, WM_NCCREATE is sent first, so my static windows procedure intercepts this message and grabs the pointer from it. Once it has the pointer, the static message handler stores it in the window as user data with a call to the Windows function SetWindowLongPtr. The pointer can then be retrieved for handling subsequent messages using GetWindowLongPtr. SetWindowLongPtr and GetWindowLongPtr were introduced in a recent Microsoft Platform SDK for 64-bit Windows. If you have not yet downloaded it, you can remove the Ptr suffix and replace GWLP_USERDATA with GWL_USERDATA in the calls to get it to compile.

Dealing with Inheritance

In C++ Windows programs, new window types are typically created by deriving them from a base window class. Somehow the derived-class window must implement its own behavior in response to Windows messages; it usually can't rely on the behavior of the base class. MFC deals with this problem using a complex system of message maps. Every child class contains an array that maps messages to the functions that handle them. The idea of implementing a system of maps and macros wasn't appealing to me, so I chose a different tactic — let C++ do the work!

In the files Wndobj.h and BaseWnd.cpp (Listings 1 and 2), I define the class CWindow, which declares two window procedures. One of the procedures is static, and the other is virtual. The static function CWindow::BaseWndProc is the only one that is called by Windows. BaseWndProc calls the virtual function WindowProc, which takes an additional parameter pbProcessed, a pointer to a Boolean. WindowProc passes back a flag through this parameter to indicate whether or not it processed the message. WindowProc should be overridden by all derived classes to provide message processing.

The idea behind this design is that each child-class WindowProc first calls the WindowProc of its base class, to let it process the message, then handles the message itself. If either the child class or the base class handles the message, the child-class WindowProc should pass the value TRUE through pbProcessed; otherwise it should pass back FALSE. If neither the child or parent handles the message, BaseWndProc hands it back to Windows.

CWindow has a style of NULL, so it won't work by itself. To create a usuable window from CWindow, do the following:

1. Derive a class from CWindow (or a derived class).

2. In the constructor, set _pszClassName to a unique string, and optionally set _pszTitle and _dwStyle. Base classes are constructed first, so you can override these changes in the constructors of derived classes. You may also add Get and Set methods to take care of items such as the window title.

3. Create or override virtual message handlers. The Microsoft include file windowsx.h is a good source of prototypes.

4. If the handler for a message you want to process does not exist in any of the parent classes, override WindowProc and call it from there. Make sure you call the base class's WindowProc before handling any messages. If you do not handle the message, return the LRESULT obtained from the base class's WindowProc. Set *pbProcessed to TRUE if you handle the message. (Doing so prevents it from being passed on to the Windows default handler, DefWindowProc.)

Sample Application

Listings 3 and 4 show a sample application of the wrapper class. The code uses the method described above to create a frame window, CMainWindow. CChildWindow inherits the style attributes from CMainWindow's class, and overrides WindowProc to draw in the client area. To save typing, I used the message cracker macros provided in windowsx.h.

Note that RegisterClassEx can be called more than once per Windows class. It will fail on each call after the first, but the window will still be created. For the sake of simplicity, my code does not attempt to prevent multiple registrations. If this concerns you, you can use the Windows functions AddAtom and FindAttom to keep track of whether the class name was registered. If the registration fails for some reason (such as a duplicate class name), CWindow::Create will return NULL. In this implementation, it is the responsibility of the caller to check the value returned by CWindow::Create.

Problems can also occur if you need to use the window's user data for something else. Real MDI (Multiple Document Interface) windows, for example, need the last void * parameter of CreateWindowEx for extra data of their own. For situations like this, you will have to use a static data structure to map the window handles to the pointers to the objects that handle them.

I have used the method described in this article for everything from wrapping dialogs and property sheets to building custom controls and entire applications. It combines the benefits of inheritance and object-oriented programming with the raw power of the Win32 API.

Steve Hanov currently works at Quack.com and is a candidate for a BMath in Computer Science at the University of Waterloo, Ontario. He is creating a skin framework for Windows applications in his spare time. When he's not pushing the limits of Win32, he enjoys developing for Linux. Feel free to contact Steve at [email protected]


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.