Click here to Skip to main content
Click here to Skip to main content

Pluggable Event Handler

, 9 Jun 2000
Rate this:
Please Sign up or sign in to vote.
A plug in class that allows you to intercept and handle messages for any window class
  • Download demo project - 266 Kb
  • <!-- Article Starts -->

    Problem

    You've written some great new functionality for a CDialog. Upon reflection, it seems that this is functionality you can use over and over again in other projects. So you quickly wrap it up in a base class, CMyCoolNewDlg. You reuse this base class a few times and then suddenly find yourself needing the same functionality for a CFormView. Sighing over the fact that you can't just reuse all of the code you already have, you start to cut and paste all of the functionality from CMyCoolNewDlg into a new CMyCoolNewFormView. Repeat these same steps later when you need a CDialogBar with this functionality. Eventually, you have base classes for all of MFC's basic window types and you feel relatively safe. Until… you have to use CXYZCompanyDlg in a project and need this class to have the same functionality as well. Boy, what an ugly can of worms.


    Solution

    So, how do we handle this problem? Actually, it's pretty simple. What we want to do is create a base class that instead of representing a specific window type it simply wraps event handling functionality. This base class needs some mechanism to "hook" itself onto the event handling mechanism associated with a window and from that point on it can process events for the window. For specifics on how this is done, skip down to the "Programming Notes" section below. For now, we'll just discuss how to use this base class.

    Just for an example, let us suppose we have the need to restore some windows to the same position each time they are created and destroyed. This is pretty simple functionality, and we've all probably programmed it into specific windows over and over again. This is a good candidate for a "pluggable" event handler. Below I present the code for a CPersistantSize class which will easily allow us to add this functionality to any window.

    class CPersistantSize : protected CSubclassWnd
    {
    public:
    	CPersistantSize(HWND hWnd, LPCTSTR szSection);
    
    protected:
    	BEGIN_MSG_DISPATCH(CPersistantSize, CSubclassWnd)
    		DISPATCH_MSG(WM_DESTROY, OnDestroy)
    	END_MSG_DISPATCH()
    	void OnDestroy();
    
    private:
    	CString m_sSection;
    };
    
    CPersistantSize::CPersistantSize(LPCTSTR szSection)
    	: m_sSection(szSection)
    {
    	SubclassWindow(hWnd);
    	CRect rect;
    	if ((rect.left = theApp.GetProfileInt(m_sSection, "Left", -1)) == -1)
    		return;
    	if ((rect.right = theApp.GetProfileInt(m_sSection, "Right", -1)) == -1)
    		return;
    	if ((rect.top = theApp.GetProfileInt(m_sSection, "Top", -1)) == -1)
    		return;
    	if ((rect.bottom = theApp.GetProfileInt(m_sSection, "Bottom", -1)) == -1)
    		return;
    	MoveWindow(hWnd, rect.left, rect.top, rect.Width(), rect.Height(), TRUE);
    }
    
    void CPersistantSize::OnDestroy()
    {
    	HWND hWnd = GetHandle();
    	CRect rect;
    	GetWindowRect(hWnd, &rect);
    	theApp.WriteProfileInt(m_sSection, "Left", rect.left);
    	theApp.WriteProfileInt(m_sSection, "Right", rect.right);
    	theApp.WriteProfileInt(m_sSection, "Top", rect.top);
    	theApp.WriteProfileInt(m_sSection, "Bottom", rect.bottom);
    	DefWindowProc();	// Make sure to call the "base" functionality
    }
    

    That's it. With this class in hand you can modify a dialog to be persistent by simply creating a new CPersistantSize instance in the dialog's OnInitDialog (or some other appropriate message handler):

    BOOL CMyDialog::OnInitDialog()
    {
    	// Other code removed for brevity…
    
    	new CPersistantSize(GetSafeHwnd(), "MySection");
    }
    

    That's it. Now CMyDialog will have a persistant size. This is an extremely simple example, but you can see how you can extend the idea to other problems using the CSubclassWnd base class.


    Message Reflection

    Message reflection lets you handle messages for a control, such as WM_CTLCOLOR, WM_COMMAND, and WM_NOTIFY, within the control itself. This makes the control more self-contained and portable.

    Message reflection requires help from a control's parent, who must first "reflect" many messages to the control before attempting to handle the message itself. MFC and ATL have their own ways of handling message reflection, but for CSubclassWnd we need our own mechanism for handling this. CSubclassWnd sets up things for message reflection for you automatically, though you can disable this by passing an extra parameter to the SubclassWindow method. Once this is done the message handler will receive reflected messages through OCM_* messages. For instance, if you want to handle a reflected WM_CTLCOLORBTN message, simply add a handler for OCM_CTLCOLORBTN. The example application that accompanies this article shows how this is done by changing the color for a list box using the CSubclassWnd derived class CColorMgr. See the source code for details.


    Programming Notes

    I must admit that I borrowed much of the code for this from various places. First of all, Paul DiLascia wrote a wonderful article for Microsoft Systems Journal about this very topic called "More Fun With MFC: DIBs, Palettes, Subclassing and a Gamut of Goodies, Part II" (this article is now available on MSDN as well). He came up with a very similar class that he called CMsgHook. My implementation is quite different from his, even if the classes are very similar. In particular, the problems I had with his implementation were that it relied heavily on MFC and paid little attention to threading issues. I wanted my implementation to be useable even when MFC wasn't being used. Further, I wanted to insure that you could easily and safely pass around these "message handling" objects from thread to thread. After all, C programmers don't have to worry about threading issues when they subclass a window, so why should C++ programmers?

    Threading issues exist because the method Mr. DiLascia used was similar to the MFC method of retaining a mapping from HWND to CWnd pointers (CMsgHook pointers in his case) so that the WNDPROC, which is a C callback, could eventually make a call on an object method. This is a simple and elegant technique, but it's not thread safe. MFC (and CMsgHook) make it thread safe by having the map reside in thread local storage. Unfortunately, this means that if you pass the object to another thread, the mapping doesn't exist in that thread, which can cause some serious and hard to understand bugs.

    So, my implementation had to do without the mapping scheme. As a bonus, by eliminating the map I can speed up the implementation since we don't have to do a map lookup every time a message comes in.

    To eliminate the map I borrowed an idea from ATL (in fact, I came very close to just stealing code from there). The CWindow class and it's set of related classes in ATL use a unique method of subclassing a window so that a WNDPROC can map to an object's method. Basically, instead of replacing a window's WNDPROC with a new one, you replace it with an "assembly language thunk" instead. It's the responsibility of the thunk to call the object's method. This thunk is, though short, rather complex. Instead of going into detail about it here you should read the article "Thunking WndProcs in ATL" by Fritz Onion, published in C++ Report and available (at least for now) online at http://www.develop.com/hp/onion/Articles/cpprep0399.htm.

    So, if ATL already has the capability to do this same thing, why didn't I just use CWindow instead? I could have done this. In fact Mihai Filimon did just this for his CPictureWindow class in the article "Adding a background image to any window" which can be found on The Code Project. This works, but I think it's too heavy handed for two reasons. First of all, it's an ATL class, so it requires that you include ATL within your application. This isn't a big problem since ATL was designed to be small and lightweight, unlike MFC. However, it does mean we have to create an ATL CComModule instance. If you are truly using ATL, this isn't a big deal. However, for a "pluggable" class it seems like a bit of a nuisance. Secondly, the ATL approach defines a full framework for creating and using window classes. A "pluggable" message handler doesn't need all of the extra baggage. So I preferred to just "borrow" the idea from ATL and implement it in a standalone class.

    Most of the implementation was borrowed directly from ATL, but was stripped down to the bare bones and cleaned up a bit. The biggest area in which improvements were made was in the message dispatching architecture. ATL's approach used a message map similar to MFC's. However, ATL's implementation of the message map simply translated a few macros into an actual "if then else" construct within a virtual function. This approach is cleaner and probably produces slightly faster code. However, ATL's implementation has a few "warts". First of all, the MESSAGE_HANDLER macro doesn't translate the lParam and wParam parameters into more meaningful types (a process known as "message cracking" to C programmers), so the handler functions all have the same unwieldy syntax. Secondly, ATL's implementation requires you to explicitly chain these "message maps" with the macro CHAIN_MSG_MAP. This can be a source of error.

    The header file <SubclassWnd.h> defines several message-dispatching macros similar to those found in ATL that define the message handling dispatch code. You saw several of them in the example above (and several more were used that you can't see directly). BEGIN_MSG_DISPATCH is used to start a message dispatch map and requires two parameters: the current class name and the base class name. If you look at the definition of this macro you will notice that the class name is never used. This may seem strange, but it's consistent with the ATL macro (which also doesn't use the class name parameter) and allows us the possibility for expansion in a later release. Unlike the ATL macro, mine requires the base class name, which will be used to chain the message map automatically. Because the chaining won't happen until after we dispatch the messages to all handlers in the current class I had to somehow save this parameter for use by the END_MSG_DISPATCH macro.

    You can't save parameters like this with the C preprocessor, so I had to resort to a typedef. Unfortunately, you can't use the token-pasting macro in the preprocessor to create this typedef. The following code might look logical, but it won't work.

    	typedef ##rootClass root_type;
    

    The reason it won't work is that the token-pasting macro would paste the "rootClass" parameter directly to the preceding token, resulting in the following code after preprocessing (assuming the second parameter was CSubclassWnd):

    	typedef CSubclassWnd root_type;
    

    So, I had to find a work around. In this case I used a simple template class, DISPATCH_TRAITS. This template class has one purpose in life: to hold the typedef for its generic type. This can then be used to create a local typedef within our code. For the implementation look at the supplied code.

    All that's left is the macro DISPATCH_MSG and its helper macros. DISPATCH_MSG is very similar to the HANDLE_MSG macro found in <windowsx.h>, and through the help of other macros it translates a message into a call to a meaningful member function call. In other words, it's a C++ style message cracker. This approach simplifies message handling code and is easily extendable to user defined message types. You just have to supply the corresponding DISPATCH_* macro, where * is the symbolic message id such as WM_MYMESSAGE.

    That covers most of the interesting points about the implementation. I hope everyone can find a use for this class.


    History

    Version Date Comments
    1.0 10 Dec 1999 - 7 Jun 2000 First release version. General "plugin" capability, based on concepts from ATL but with no reliance on any library other than the Win32 SDK.
    2.0 7 Jun 2000 First major revision. Now integrates more directly with MFC, with overloaded SubclassWindow that takes a CWnd*, while retaining full support of compiling with out MFC. Uses Debug.h to eliminate any need for MFC debugging macros. Reimplemented UnsubclassWindow to be more robust (no longer requires in-order calls to unsubclass multiple plugins). Added support for message reflection. Added support for registered messages within the message map macros. Updated the article to reflect changes in CPersistantSize that reflect a "better" implementation. Modified comments in the code to use "JavaDoc" style comments, which can be sent through DoxyGen to create documentation on code useage. Documentation generated by DoxyGen is included in the ZIP file. Note: This version was a major revision, and though all attempts were made to minimize impact to the interface, full backwards compatibility may not exist. Work derived from this library may require minor modification to use the new version.


    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

    Share

    About the Author

    William E. Kempf
    Web Developer
    United States United States
    Windows developer with 10+ years experience working in the banking industry.

    Comments and Discussions

     
    QuestionNice Work! Why protected dtor? PinmemberTim Finer21-Mar-02 7:22 
    AnswerRe: Nice Work! Why protected dtor? Pinmemberpeterchen23-Jan-07 10:30 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

    | Advertise | Privacy | Mobile
    Web04 | 2.8.140827.1 | Last Updated 10 Jun 2000
    Article Copyright 1999 by William E. Kempf
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Service
    Layout: fixed | fluid