Click here to Skip to main content
15,896,606 members
Articles / Programming Languages / C++

Observing the world - how to build a reuseable implementation of a design pattern

Rate me:
Please Sign up or sign in to vote.
4.58/5 (11 votes)
11 Jun 200111 min read 94.5K   397   86  
An article about the techniques to pour reusable design (a design pattern) into reusable code with an example of the observer pattern.
/** @file SubclassWnd.cpp
 *
 * Implementation file for CSubclassWnd.
 *
 * @author William E. Kempf
 * @version 2.0
 *
 * Copyright &copy 2000 NEWare Software.
 *
 * This code may be used in compiled form in any way you desire (including
 * commercial use). The code may be redistributed unmodified by any means providing
 * it is not sold for profit without the authors written consent, and providing that
 * this notice and the authors name and all copyright notices remains intact. However,
 * this file and the accompanying source code may not be hosted on a website or bulletin
 * board without the authors written permission.
 *
 * <b><i>This software is provided "as is" without express or implied warranty. Use it
 * at your own risk!</i></b>
 */

#include "stdafx.h"
#include "Debug.h"
#include "SubclassWnd.h"

namespace
{
	/*
	 * Sends a message to all child controls of a specified window.
	 *
	 * @param message [in] Specifies the message id.
	 * @param wParam [in] Specifies additional message-specific information.
	 * @param lParam [in] Specifies additional message-specific information.
	 * @param bDeep [in] Specifies whether or not the message should be sent
	 *		recursively to all children.
	 */

	void SendMessageToDescendantsImp(HWND hWnd, UINT message, WPARAM wParam,
		LPARAM lParam, BOOL bDeep)
	{
		// walk through HWNDs to avoid creating temporary CWnd objects
		// unless we need to call this function recursively
		for (HWND hWndChild = ::GetTopWindow(hWnd); hWndChild != NULL;
			hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT))
		{
			// send message with Windows SendMessage API
			::SendMessage(hWndChild, message, wParam, lParam);
			if (bDeep && ::GetTopWindow(hWndChild) != NULL)
			{
				// send to child windows after parent
				SendMessageToDescendantsImp(hWndChild, message, wParam, lParam,
					bDeep);
			}
		}
	}
};

/*
 * Message used to unsubclass CSubclassWnd objects.
 */
const UINT WM_UNSUBCLASS = RegisterWindowMessage("{207D9568-FF3C-11D3-A469-000629B2F855}");
/*
 * Message used to reflect messages back to child controls.
 */
const UINT WM_REFLECT = RegisterWindowMessage("{9688DAE5-3736-11D4-A48C-000629B2F855}");
/*
 * Message used to get the reflector plugin from a window if one exists.
 */
const UINT WM_GETREFLECTOR = RegisterWindowMessage("{9688DAE6-3736-11D4-A48C-000629B2F855}");

/*
 * Specifies the reflected message information.  Used by WM_REFLECT.
 */
struct MSGREFLECTSTRUCT
{
	UINT message;
	WPARAM wParam;
	LPARAM lParam;
	BOOL bHandled;
};

/*
 * Message handler used to reflect messages from a parent window to a child window.
 */
class CMessageReflector : public CSubclassWnd
{
private:
	CMessageReflector(HWND hWnd);

public:
	static CMessageReflector* FromHandle(HWND hWnd);
	virtual BOOL ProcessWindowMessage(UINT message, WPARAM wParam, LPARAM lParam,
		LRESULT& lResult);
	virtual void OnFinalMessage();
};

/*
 * Constructor.  Subclasses the specified window.
 *
 * @param hWnd [in] Handle to the window to be subclassed.
 */

CMessageReflector::CMessageReflector(HWND hWnd)
{
	ASSERT(::IsWindow(hWnd));
	SubclassWindow(hWnd, FALSE);
}

/*
 * This method retrieves a pointer to a CMessageReflector object from a handle to a
 * window.  If the specified window has not yet been subclassed by a CMessageReflector
 * instance then a new instance is created on the heap.
 *
 * @param hWnd [in] Handle to the window to retrieve the CMessageReflector from.
 *
 * @return A pointer to the message reflector associated with the window.
 */

CMessageReflector* CMessageReflector::FromHandle(HWND hWnd)
{
	CMessageReflector* pReflector =
		(CMessageReflector*)::SendMessage(hWnd, WM_GETREFLECTOR, 0, 0);
	if (!pReflector)
		pReflector = new CMessageReflector(hWnd);
	return pReflector;
}

/*
 * Called to handle window messages.  Derived classes can override this
 * method to handle any window messages sent to the subclassed window.
 * Normally the programmer won't code this directly, but will instead use
 * the BEGIN_MSG_DISPATCH/END_MSG_DISPATCH macros.
 *
 * @param message [in] Specifies the message id.
 * @param wParam [in] Specifies additional message-specific information.
 * @param lParam [in] Specifies additional message-specific information.
 * @param lResult [in] A reference to the result value to be returned to
 *		the caller.
 *
 * @return TRUE if the message was handled, otherwise FALSE.
 */

BOOL CMessageReflector::ProcessWindowMessage(UINT message, WPARAM wParam, LPARAM lParam,
											 LRESULT& lResult)
{
	if (message == WM_GETREFLECTOR)
	{
		lResult = (LRESULT)this;
		return TRUE;
	}

	HWND hWndChild = NULL;

	switch (message)
	{
	case WM_COMMAND:
		if (lParam != NULL)	// not from a menu
			hWndChild = (HWND)lParam;
		break;
	case WM_NOTIFY:
		hWndChild = ((LPNMHDR)lParam)->hwndFrom;
		break;
	case WM_PARENTNOTIFY:
		switch (LOWORD(wParam))
		{
		case WM_CREATE:
		case WM_DESTROY:
			hWndChild = (HWND)lParam;
			break;
		default:
			hWndChild = GetDlgItem(GetHandle(), HIWORD(wParam));
			break;
		}
		break;
	case WM_DRAWITEM:
		if (wParam)		// not from a menu
			hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;
		break;
	case WM_MEASUREITEM:
		if (wParam)		// not from a menu
			hWndChild = GetDlgItem(GetHandle(), ((LPMEASUREITEMSTRUCT)lParam)->CtlID);
		break;
	case WM_COMPAREITEM:
		if (wParam)		// not from a menu
			hWndChild = GetDlgItem(GetHandle(), ((LPCOMPAREITEMSTRUCT)lParam)->CtlID);
		break;
	case WM_DELETEITEM:
		if (wParam)		// not from a menu
			hWndChild = GetDlgItem(GetHandle(), ((LPDELETEITEMSTRUCT)lParam)->CtlID);
		break;
	case WM_VKEYTOITEM:
	case WM_CHARTOITEM:
	case WM_HSCROLL:
	case WM_VSCROLL:
		hWndChild = (HWND)lParam;
		break;
	case WM_CTLCOLORBTN:
	case WM_CTLCOLORDLG:
	case WM_CTLCOLOREDIT:
	case WM_CTLCOLORLISTBOX:
	case WM_CTLCOLORMSGBOX:
	case WM_CTLCOLORSCROLLBAR:
	case WM_CTLCOLORSTATIC:
		hWndChild = (HWND)lParam;
		break;
	default:
		break;
	}

	if (hWndChild == NULL)
		return FALSE;

	ASSERT(::IsWindow(hWndChild));

	MSGREFLECTSTRUCT rs;
	rs.message = OCM__BASE + message;
	rs.wParam = wParam;
	rs.lParam = lParam;
	rs.bHandled = FALSE;

	lResult = ::SendMessage(hWndChild, WM_REFLECT, 0, (LPARAM)&rs);

	return rs.bHandled;
}

/*
 * Called after the last message has been dispatched to the window.
 */

void CMessageReflector::OnFinalMessage()
{
	delete this;
}

/**
 * Default contructor.
 */

CSubclassWnd::CSubclassWnd()
	: m_hWnd(0), m_pfnSuperWindowProc(::DefWindowProc), m_pCurrentMsg(0)
{
}

/**
 * Destructor.
 */

CSubclassWnd::~CSubclassWnd()
{
	if (m_hWnd != NULL)
		UnsubclassWindow();
}

/**
 * Called to subclass the specified window.  If the @a bReflect
 * parameter is non-zero then the parent window will be subclassed to
 * support message reflection.
 *
 * @param hWnd [in] Specifies the handle of the window to be subclassed.
 * @param bReflect [in] Specifies whether or not the parent should
 *		reflect messages back to the window.
 *
 * @return If the window was subclassed, the return value is non-zero.  If the
 *		window was not subclassed, the return value is zero.
 */

BOOL CSubclassWnd::SubclassWindow(HWND hWnd, BOOL bReflect)
{
	if (m_hWnd != NULL || !::IsWindow(hWnd))
		return FALSE;

	m_thunk.Init(WindowProc, this);
	WNDPROC pProc = (WNDPROC)&(m_thunk.thunk);
	WNDPROC pfnWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);
	if (pfnWndProc == NULL)
		return FALSE;

	m_pfnSuperWindowProc = pfnWndProc;
	m_hWnd = hWnd;

	if (bReflect && (GetWindowLong(hWnd, GWL_STYLE) & WS_CHILD) == WS_CHILD)
	{
		HWND hWndParent = GetParent(hWnd);
		ASSERT(::IsWindow(hWndParent));
		CMessageReflector::FromHandle(hWndParent);
	}

	return TRUE;
}

/**
 * Called to unsubclass the specified window.
 *
 * @return The handle to the window previously subclassed.
 */

HWND CSubclassWnd::UnsubclassWindow()
{
	return (HWND)SendMessage(WM_UNSUBCLASS, 0, (LPARAM)this);
}

/**
 * Implementation detail.  This is the actual WindowProc installed by
 * the thunk.
 *
 * @param hWnd [in] Handle to the window.
 * @param message [in] Specifies the message id.
 * @param wParam [in] Specifies additional message-specific information.
 * @param lParam [in] Specifies additional message-specific information.
 *
 * @return The return value is the result of the message processing and
 *		depends on the message sent.
 */

LRESULT CALLBACK CSubclassWnd::WindowProc(HWND hWnd, UINT message, WPARAM wParam,
										  LPARAM lParam)
{
	CSubclassWnd* pThis = (CSubclassWnd*)hWnd;

	// set a ptr to this message and save the old value
	MSG msg = { pThis->m_hWnd, message, wParam, lParam, 0, { 0, 0 } };

	// check to see if this is a reflected message and adjust accordingly
	MSGREFLECTSTRUCT* rs = 0;
	if (message == WM_REFLECT)
	{
		rs = (MSGREFLECTSTRUCT*)lParam;
		ASSERT(rs);
		msg.message = rs->message;
		msg.wParam = rs->wParam;
		msg.lParam = rs->lParam;
	}

	// save the message information and set the new current
	const MSG* pOldMsg = pThis->m_pCurrentMsg;
	pThis->m_pCurrentMsg = &msg;

	// pass to the message map to process
	LRESULT lRes;
	BOOL bRet = pThis->ProcessWindowMessage(msg.message, msg.wParam, msg.lParam, lRes);

	// if this is a reflected message let parent know if it was handled
	if (rs)
		rs->bHandled = bRet;

	// restore saved value for the current message
	pThis->m_pCurrentMsg = pOldMsg;

	// do the default processing if message was not handled
	// note that we need to use the original values, so that reflected
	// messages can properly set the 'bHandled' flag.
	if (!bRet)
		lRes = pThis->DefWindowProc(message, wParam, lParam);

	if (message == WM_NCDESTROY)
	{
		// unsubclass, if needed
		pThis->UnsubclassWindow();

		// clean up after window is destroyed
		pThis->OnFinalMessage();
	}

	return lRes;
}

/**
 * Sends a message to all child windows of the subclassed window.
 *
 * @param message [in] Specifies the message id.
 * @param wParam [in] Specifies additional message-specific information.
 * @param lParam [in] Specifies additional message-specific information.
 * @param bDeep [in] Specifies whether or not the message should be sent
 *		recursively to all children.
 */

void CSubclassWnd::SendMessageToDescendants(UINT message, WPARAM wParam, LPARAM lParam,
											BOOL bDeep)
{
	SendMessageToDescendantsImp(GetHandle(), message, wParam, lParam, bDeep);
}

/**
 * Called after the last message has been dispatched to the window.
 */

void CSubclassWnd::OnFinalMessage()
{
}

/**
 * Called to handle window messages.  Derived classes can override this
 * method to handle any window messages sent to the subclassed window.
 * Normally the programmer won't code this directly, but will instead use
 * the #BEGIN_MSG_DISPATCH/#END_MSG_DISPATCH macros.
 *
 * @param message [in] Specifies the message id.
 * @param wParam [in] Specifies additional message-specific information.
 * @param lParam [in] Specifies additional message-specific information.
 * @param lResult [out] A reference to the result value to be returned to
 *		the caller.
 *
 * @return If the message was handled, the return value is non-zero.  If the
 *		message was not handled, the return value is zero.
 */

BOOL CSubclassWnd::ProcessWindowMessage(UINT message, WPARAM wParam, LPARAM lParam,
										LRESULT& lResult)
{
	if (message == WM_UNSUBCLASS)
	{
		if ((CSubclassWnd*)lParam == this)
		{
			if (m_hWnd == NULL)
				lResult = NULL;
			else
			{
				if (wParam)
				{
					CSubclassWnd* pPrevious = (CSubclassWnd*)wParam;
					ASSERT(pPrevious->m_pfnSuperWindowProc == (WNDPROC)&(m_thunk.thunk));
					pPrevious->m_pfnSuperWindowProc = m_pfnSuperWindowProc;
					lResult = (LRESULT)m_hWnd;
					m_pfnSuperWindowProc = ::DefWindowProc;
					m_hWnd = NULL;
				}
				else
				{
					WNDPROC pOurProc = (WNDPROC)&(m_thunk.thunk);
					WNDPROC pActiveProc = (WNDPROC)::GetWindowLong(m_hWnd, GWL_WNDPROC);
					ASSERT(pOurProc == pActiveProc);
					if (!::SetWindowLong(m_hWnd, GWL_WNDPROC, (LONG)m_pfnSuperWindowProc))
						lResult = NULL;
					else
					{
						m_pfnSuperWindowProc = ::DefWindowProc;
						lResult = (LRESULT)m_hWnd;
						m_hWnd = NULL;
					}
				}
			}
		}
		else
			lResult = DefWindowProc(message, (WPARAM)this, lParam);

		return TRUE;
	}

	return FALSE;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Germany Germany
Daniel Lohmann (daniel@losoft.de) is Assistant Professor at the Distributed Systems and Operating Systems department at Friedrich-Alexander-University Erlangen-Nuremberg, Germany. His main research topic is the design of a highly customizable and scalable operating system product line for deeply embedded systems using static configuration and aspect-oriented techniques. Before joining Universität Erlangen he worked as a freelance trainer and consultant for NT system programming, advanced C++ programming and OOA/OOD. He is interested in upcoming programming techniques like aspect-oriented programming, generative programming and C++ meta coding and has written some nice and handy tools for Windows NT which you can download at his web site.

Comments and Discussions