Event UI and the Events IDispatch

Now that we have an event map, we need a way to assign actions to be stored in the map and an implementation of IDispatch to trigger those actions when events occur. This is the point at which a container implementer should think about the possible uses of IOleControl::FreezeEvents, wrapping any risky nonreentrant event handling code with calls to FreezeEvents(TRUE) and FreezeEvents(FALSE).

As I said earlier, each tenant that contains a control manages an instance of CEventMap. When you select the Events menu item, Patron invokes the Control Events dialog box, passing a pointer to the event map owned by the currently selected tenant. This pointer shows up in the dialog procedure EventsDlgProc, found in EVENTS.CPP. The dialog is really a user interface for working with the contents of an event map, which is used initially to fill the dialog's list box. Whenever you select an event in the list, the dialog procedure calls CEventMap::Get to retrieve the action assigned to that event's dispID and uses that action to check the correct radio button. When you change the selected sound assignment, the dialog calls CEventMap::Set to store it in the map for the event's dispID. Obviously, this user interface is simplistic, but it gives you an idea of how action assignment happens.

Given a populated event map, a tenant's implementation of IDispatch has something to work with. To receive events, of course, we first have to connect this IDispatch to the control as an outgoing interface. This involves retrieving the IID of the event set, going through the control's IConnectionPointContainer to find a connection point for that IID, and then passing our event sink interface to IConnectionPointContainer::Advise. All this happens with the functions ObjectEventsIID and InterfaceConnect, both of which are found in CONNECT.CPP. (The InterfaceDisconnect function is also used for terminating the connection.) Both functions are straightforward uses of their respective interfaces.

A tenant's event sink is implemented by using the class CDispatchEvents. A tenant will create an instance of this class as necessary. (See CTenant::ControlInitialize.) CDispatchEvents is derived from IDispatch, so it receives interface calls directly. Now, because an events interface is defined by the control itself, we have no need to implement any of the IDispatch members except Invoke. It would be crazy for a control that already knows the type information for its own outgoing interfaces to call IDispatch::GetTypeInfo, which would, if implemented, only turn around and ask the object for its type information. So Patron simply returns E_NOTIMPL from this function as well as for GetTypeInfoCount and GetIDsOfNames.

Invoke is where the fun happens. All we need to do is scan the event mappings held in whatever tenant is attached to the instance of CDispatchEvents that is called. If we find a match, we execute the action, which in our case means calling MessageBeep:


STDMETHODIMP CDispatchEvents::Invoke(DISPID dispIDMember, REFIID riid
, LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
HRESULT hr;
VARIANT varResult;
EVENTACTION iAction;
UINT i;
PEVENTMAP pEM;

if (IID_NULL!=riid)
return ResultFromScode(E_INVALIDARG);

if(NULL==pVarResult)
pVarResult=&varResult;

VariantInit(pVarResult);
V_VT(pVarResult)=VT_EMPTY;

//Only method calls are valid.
if (!(DISPATCH_METHOD & wFlags))
return ResultFromScode(DISP_E_MEMBERNOTFOUND);

iAction=ACTION_NONE;
pEM=m_pTen->m_pEventMap->m_pEventMap;

for (i=0; i < m_pTen->m_pEventMap->m_cEvents; i++)
{
if (dispIDMember==pEM[i].id)
{
iAction=pEM[i].iAction;
break;
}
}

if (ACTION_NONE==iAction)
hr=ResultFromScode(DISP_E_MEMBERNOTFOUND);
else
{
MessageBeep((UINT)iAction);
hr=NOERROR;
}

return hr;
}

I suspect that most event handling code for Invoke will look a lot like this. What you do after you find a matching dispID, however, and how you then execute the action will be a lot more complicated. This is especially true if you handle arguments that accompany the event, which a sophisticated container will do, as well as event return values. Such a container would be more interesting (and marketable) than a container that doesn't do anything more than beep, tweak, ding, and frazzle when you twiddle controls.