Event listeners subscribe to event routers to receive events. The IEventListener interface defines a single method, HandleEvent(), as shown in Example 40.
class __declspec(uuid("47E1CE38-D500-11d2-8CAB-0010A4F36466")) IEventListener : public IRefCount, public IQueryGuid { public: /* Receive an event and attempt to handle it. */ virtual bool HandleEvent(IEvent* pIEvent) = 0; }; |
An event listener can implement HandleEvent() any way it chooses, but the typical implementation invokes the event's Dispatch() method. Example 41 demonstrates a typical implementation of HandleEvent().
virtual bool HandleEvent(IEvent* pIEvent) { bool bHandled = false; if (pIEvent != NULL) bHandled = pIEvent->Dispatch(this); return bHandled; } |
Notice that the event listener passes a pointer to itself into the event object's Dispatch() method. The Dispatch() method queries the listener for an interface that it understands and then invokes the callback method on that interface. Example 42 demonstrates how the paint event class invokes the OnPaint() callback function.
bool CWindowPaintEvent::Dispatch(IQueryGuid* pIListener) { bool bHandled = false; IWindowListener* pIWindowListener = guid_cast<IWindowListener*>(pIListener); if (pIWindowListener != NULL) { bHandled = pIWindowListener->OnPaint(GetDC()); pIWindowListener->Release(); } return bHandled; } |
The event's Dispatch() method checks to see if it has the right type of listener. If it does have the right listener, it invokes the appropriate callback method. In the preceding sample code, the event listener is expected to implement the IWindowListener interface to receive the OnPaint() callback.
Although HandleEvent() method is sufficient for handling all the events that a listener is interested in receiving, doing so would be equivalent to writing a window procedure containing a big switch() statement. The idea behind making event handling simpler is to map events onto individual member functions. For example, you could map a WM_PAINT message onto an OnPaint() member function that receives a device context as a parameter. Event listener interfaces extend the base IEventListener interface with callback functions that are invoked to handle events. An example of an event listener interface is IWindowListener, shown in Example 43.
class __declspec(uuid("A67C846D-0A0A-4b5e-8DC0-3DA18454F582")) IWindowListener : public IEventListener { public: virtual bool OnCreate(LPCREATESTRUCT lpCreateStruct) = 0; virtual bool OnDestroy() = 0; virtual bool OnMove(int x, int y) = 0; virtual bool OnSize(UINT nFlag, int cx, int cy) = 0; virtual bool OnEraseBkgnd(HDC hDC) = 0; virtual bool OnPaint(HDC hDC) = 0; virtual bool OnWindowPosChanging(LPWINDOWPOS lpWindowPos) = 0; virtual bool OnWindowPosChanged(LPWINDOWPOS lpWindowPos) = 0; virtual bool OnTimer(UINT nIDTimer) = 0; }; |
The HandleEvent() method inherited from IEventListener is called to handle all events, but it delegates the work to the callback functions by calling the event object's Dispatch() method. The flow of control for handling events is as follows:
Event object->HandleEvent->Dispatch->Callback function |
The event object is passed to the event listener's HandleEvent() method. The HandleEvent() method delegates the task of invoking the correct callback function to the event object. Be aware that this is only the default behavior, and can easily be overridden. For example, you might handle certain events directly in your event listener's HandleEvent() method without invoking a callback function. It is also possible to implement HandleEvent() in such a way that it bypasses the event object's Dispatch() method and invokes the callback methods directly. The architecture is flexible enough to allow many different event listener implementations.
Implementing the HandleEvent() method along with each callback function every time you want to write an event listener is time-consuming. Adapter classes provide default implementations of event listener interfaces. In addition to implementing HandleEvent() and the event listener callback methods, adapters implement the QueryGuid(), AddRef(), and Release() methods inherited from the IQueryGuid and IRefCount interfaces. The CEventListenerBase class is a generic base class that you can use to implement adapters. It provides implementations of the HandleEvent(), QueryGuid(), AddRef(), and Release() methods. The CEventListenerBase implementation of HandleEvent() is the typical one described in Section 6.4, and is as follows:
virtual bool HandleEvent(IEvent* pIEvent) { bool bHandled = false; if (pIEvent != NULL) bHandled = pIEvent->Dispatch(this); return bHandled; } |
The CEventListenerBase class is a template that takes the event listener interface as an argument, and then derives itself from the given event listener interface. The CWindowAdapter class uses the CEventListenerBase to provide a default implementation of the IWindowListener interface, as shown in Example 44.
class CWindowAdapter : public CEventListenerBase<IWindowListener> { public: virtual bool OnCreate(LPCREATESTRUCT lpCreateStruct) { return false; } virtual bool OnDestroy() { return false; } virtual bool OnMove(int x, int y) { return false; } virtual bool OnSize(UINT nFlag, int cx, int cy) { return false; } virtual bool OnEraseBkgnd(HDC hDC) { return false; } virtual bool OnPaint(HDC hDC) { return false; } virtual bool OnWindowPosChanging(LPWINDOWPOS lpWindowPos) { return false; } virtual bool OnWindowPosChanged(LPWINDOWPOS lpWindowPos) { return false; } virtual bool OnTimer(UINT nIDTimer) { return false; } }; |
The adapter class provides basic stub implementations of each callback function, so that developers don't have to implement every single callback function in their own event listener classes.
Now let's implement a class that listens for events. Our example will be a class that paints the text "Hello World" in the center of a window. The first step is to mix in the CWindowAdapter class so that our class can listen for paint and size events.
class CHelloObject : public CWindowAdapter { POINT m_ptCenter; public: virtual bool OnSize(UINT nFlag, int cx, int cy) { m_ptCenter.x = cx / 2; m_ptCenter.y = cy / 2; } virtual bool OnPaint(HDC hDC) { SIZE szText; GetTextExtentPoint(hDC, _T("Hello World"), 11, &szText); int x = (m_ptCenter.x + szText.cx) / 2; int y = (m_ptCenter.y + szText.cy) / 2; TextOut(hDC, x, y, _T("Hello World"), 11); } }; |
The CHelloObject class overrides the OnSize() and OnPaint() event handler functions it inherits from IWindowEvent to paint the text "Hello World" in the center of a window. Now all we need is a window capable of routing events, in which to plug an instance of the CHelloObject class. The CHelloWnd class shown in Example 46 implements IEventRouter and generates events from the ATL message map using the CEventRouterMap class.
class CHelloWnd : public CWindowImpl<CHelloWnd>, public IEventRouterImpl, public CEventRouterMap<CHelloWnd> { CHelloObject m_hello; public: // The CEventRouterMap is derived from CMessageMap and // has an implementation of ProcessWindowMessage that uses // an event factory to create events and then routes them // by calling RouteEvent. BEGIN_MSG_MAP(CHelloWnd) CHAIN_MSG_MAP(CWindowImpl<CHelloWnd>) CHAIN_MSG_MAP(CEventRouterMap<CHelloWnd>) MESSAGE_HANDLER(WM_CREATE, OnCreate) END_MSG_MAP() LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) { // Wire up the hello object to listen to window events AddListener(&m_hello); return 0; } }; |
Both MFC and ATL use message maps to avoid the overhead of using virtual functions for handling messages. For example, if MFC's CWnd class defined a virtual function for every possible message a window might receive, the v-table would be large. It would also mean that the interface to CWnd would have to change every time a new message type was added to the Windows API.
Event listeners define message handlers as virtual functions, but the monolithic v-table problem is avoided by partitioning event listeners into small interfaces. Rather than putting every event handler method into a single monolithic base class, you can mix in event listeners that are small interfaces as needed. Event listeners generally handle one event or a limited category of events. For example, the IMouseListener interface has eight virtual functions to handle every type of mouse event. If a class is only interested in mouse events, it does not have to pay the v-table overhead of having virtual functions for every other type of Windows message. Partitioning event listeners into categories and mixing them into classes as interfaces is ideal; it provides the convenience of using virtual methods for event handlers and avoids the overhead of large, monolithic v-tables.
Copyright © Rogue Wave Software, Inc. All Rights Reserved.
The Rogue Wave name and logo, and Stingray, are registered trademarks of Rogue Wave Software. All other trademarks are the property of their respective owners.
Provide feedback to Rogue Wave about its documentation.