A Lightweight Window Wrapper

Even the simplest aspects of window management can benefit from a bit of encapsulation.


August 01, 2000
URL:http://www.drdobbs.com/a-lightweight-window-wrapper/184401271

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].

August 2000/A Lightweight Window Wrapper/Listing 1

Listing 1: Declaration of CWindow

class CWindow
{
public:
    CWindow();
    HWND Create(int x, int y, int nWidth, int nHeight,
            HWND hParent, HMENU hMenu, HINSTANCE hInstance);

    HWND _hwnd;

protected:
    static LRESULT CALLBACK BaseWndProc(HWND hwnd, UINT msg,
        WPARAM wParam, LPARAM lParam);
    
    virtual LRESULT WindowProc(HWND hwnd, UINT msg,
        WPARAM wParam, LPARAM lParam, PBOOL pbProcessed);

    WNDCLASSEX _WndClass;
    DWORD _dwExtendedStyle;
    DWORD _dwStyle;
    LPSTR _pszClassName;
    LPSTR _pszTitle;
};  

class CMainWindow : public CWindow
{
public: 
    CMainWindow();
};

class CChildWindow : public CMainWindow
{
public:
    CChildWindow();

protected:
    virtual LRESULT WindowProc(HWND hwnd, UINT msg,
        WPARAM wParam, LPARAM lParam, PBOOL pbProcessed);

    virtual void OnDestroy(HWND hwnd);
    virtual void OnPaint(HWND hwnd);
};
August 2000/A Lightweight Window Wrapper/Listing 2

Listing 2: Implementation of base class

#include <windows.h>
#include "WndObj.h"

CWindow::CWindow()
{
    //Set the default data for the window class.
    //These can be reset in the derived class's constructor.

    _WndClass.cbSize = sizeof(_WndClass);
    _WndClass.style = CS_DBLCLKS;
    _WndClass.lpfnWndProc = BaseWndProc;
    _WndClass.cbClsExtra = 0;
    _WndClass.cbWndExtra = 0;
    _WndClass.hInstance = NULL; 
    _WndClass.hIcon = NULL;
    _WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    _WndClass.hbrBackground = 
        (HBRUSH)GetStockObject(WHITE_BRUSH);
    _WndClass.lpszMenuName = NULL;
    _WndClass.hIconSm = NULL;

    _dwExtendedStyle = NULL;
    _dwStyle = NULL;
    _pszClassName = "CWindow";
    _pszTitle = "";
}

HWND CWindow::Create(int x, int y, int nWidth, int nHeight,
    HWND hParent, HMENU hMenu, HINSTANCE hInstance)
{
    _WndClass.lpszClassName = _pszClassName;
    _WndClass.hInstance = hInstance;
    
    //If we're already registered, this call will fail.
    RegisterClassEx(&_WndClass);

    _hwnd = CreateWindowEx(_dwExtendedStyle, _pszClassName,
        _pszTitle, _dwStyle, x, y, nWidth, nHeight,
        hParent, hMenu, hInstance, (void*)this);

    return _hwnd;
}

LRESULT CALLBACK CWindow::BaseWndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam)
{
    //A pointer to the object is passed in the CREATESTRUCT
    if(msg == WM_NCCREATE)
        SetWindowLongPtr(hwnd, GWLP_USERDATA,
        (LONG_PTR)((LPCREATESTRUCT)lParam)->lpCreateParams);
    
    BOOL bProcessed = FALSE;
    LRESULT lResult;
    
    //Retrieve the pointer
    CWindow *pObj = 
        (CWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);

    //Filter message through child classes
    if(pObj)
        lResult = pObj->WindowProc(hwnd, msg, wParam, lParam,
            &bProcessed);

    if(bProcessed)
        return lResult;
    else
    {
        //If message was unprocessed, send it back to Windows.
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}

LRESULT CWindow::WindowProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam, PBOOL pbProcessed)
{
    //This may be overridden to process messages.
    *pbProcessed = FALSE;
    return NULL;
}


August 2000/A Lightweight Window Wrapper/Listing 3

Listing 3: Sample application

#include <windows.h>
#include <windowsx.h>
#include "WndObj.h"

CMainWindow::CMainWindow()
{
    //Override class name and style attributes.
    _pszClassName = "CMainWindow";
    _pszTitle = "My Window";
    _dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
    _WndClass.style |= CS_HREDRAW | CS_VREDRAW;
}

CChildWindow::CChildWindow()
{
    //Override class name and style attributes.
    _pszClassName = "CChildWindow";
    _dwStyle |= WS_CHILD;
}

LRESULT CChildWindow::WindowProc(HWND hwnd, UINT msg,
      WPARAM wParam, LPARAM lParam, PBOOL pbProcessed)
{
    //call base class first.
    LRESULT lResult = CMainWindow::WindowProc(hwnd, msg, wParam,
        lParam, pbProcessed);
    BOOL bWasProcessed = *pbProcessed;
    *pbProcessed = TRUE;

    switch(msg)
    {
        HANDLE_MSG(hwnd, WM_DESTROY, OnDestroy);
        HANDLE_MSG(hwnd, WM_PAINT, OnPaint);

    default:
        //We did not process the message.
        //Indicate whether the base class's handler did.
        *pbProcessed = bWasProcessed;
        return lResult;
    }
}

void CChildWindow::OnDestroy(HWND hwnd)
{
    PostQuitMessage(0);
}

void CChildWindow::OnPaint(HWND hwnd)
{
    RECT rect;
    HDC hDC = GetDC(hwnd);
    PAINTSTRUCT PaintStruct;
    BeginPaint(hwnd, &PaintStruct);
    GetClientRect(hwnd, &rect);
    DrawText(hDC, "Hello, Object-Oriented World!", 29, &rect,
             DT_VCENTER | DT_CENTER | DT_SINGLELINE);
    EndPaint(hwnd, &PaintStruct);
    ReleaseDC(hwnd, hDC);
}
August 2000/A Lightweight Window Wrapper/Listing 4

Listing 4: Sample application WinMain

#include <windows.h>
#include "WndObj.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst,
                   LPSTR lpszCmdLine, int nCmdShow)
{
    CMainWindow MainWindow;
    CChildWindow ChildWindow;

    //Create the window objects.
    if(NULL == MainWindow.Create(CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance))
        return FALSE;
    
    if(NULL == ChildWindow.Create(CW_USEDEFAULT, CW_USEDEFAULT,
        350, 100, MainWindow._hwnd, NULL, hInstance))
        return FALSE;

    MSG Msg; 
    while(GetMessage(&Msg, NULL, 0, 0))
      DispatchMessage(&Msg);

    return Msg.wParam;
}

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.