After the end user has gone to the trouble of creating a form with controls, assigning actions to events, setting properties, and so on, you'd better be sure that you can save this information persistently and reload it when you load the object. As mentioned before, Patron stores its event mappings for each tenant in a stream named "\003Event Mappings" inside the object's storage.
The CEventMap::Serialize member, called from CTenant::Update, takes care of saving the information. This means writing the dispID/action pairs from its array of EVENTMAP structures and writing a terminator pair at the end of the list:
void CEventMap::Serialize(LPSTREAM pIStream)
{
EVENTMAP emTemp;
ULONG cbWrite=sizeof(DISPID)+sizeof(EVENTACTION);
if (NULL==pIStream)
return;
if (NULL!=m_pEventMap)
{
UINT i;
for (i=0; i < m_cEvents; i++)
pIStream->Write(&m_pEventMap[i], cbWrite, NULL);
}
//Write terminating entry.
emTemp.id=0;
emTemp.iAction=ACTION_TAILING;
pIStream->Write(&emTemp, cbWrite, NULL);
return;
}
Loading the event map for a control simply means reading these pairs back into memory, which happens in CEventMap::Deserialize. This function is called from CTenant::ControlInitialize (itself called from CTenant::Load) when a control is known to have events. The deserialization process ensures that the dispID still exists for the control in question and restores the assigned action if so.
void CEventMap::Deserialize(LPSTREAM pIStream)
{
if (NULL==pIStream)
return;
if (NULL==m_pEventMap)
return;
while (TRUE)
{
ULONG cbRead=sizeof(DISPID)+sizeof(EVENTACTION);
UINT i;
HRESULT hr;
EVENTMAP em;
hr=pIStream->Read(&em, cbRead, NULL);
if (FAILED(hr))
break;
//If we hit the tail, we're done.
if (ACTION_TAILING==em.iAction)
break;
//Assign action to ID if it exists.
Set(em.id, em.iAction);
}
return;
}
This code, which involves CEventMap::Set, does not assume that a control's events remain constant between instantiations. Before the tenant asks the event map to load its data from a stream, that event map will have already been initialized using the control's type information (as we do for a new control). Each call to CEventMap::Set will verify that the event dispID in question actually exists in the control's event set, and only then will it assign the action loaded from the stream.
If an event that used to exist is no longer available, Patron simply discards the action that was mapped to it. This is no big deal because the user hardly did any work to assign the action in the first place. However, if a user attached hard-written code to an event, we would not want to simply discard that code! That is a surefire way to annoy your users and draw fire from the popular press. So even though the code cannot remain attached to an event, you must still preserve it. Visual Basic, for example, copies the code to a global function. A sophisticated container will likely do something similar.