Previous Page TOC Next Page



Chapter 6


Creating OLE Control Containers


By Thomas L. Fredell

Note


The SimpleControl used in this chapter is not the same as the one used in Chapter 5. The same name was inadvertently used in both chapters.

Before OLE Custom Controls, or OCXs/ActiveX controls, the major software component container was Visual Basic. Visual Basic used a proprietary means of communicating with its components, which were called VBXs, or Visual Basic Custom Controls. Using a VBX in a C or C++ application basically meant emulating the specific control containment functionality of Visual Basic.

With the advent of the generic standard of OLE Custom Controls, there are no restrictions on the type of applications that can act as control containers. The minimum requirements for a functional control container are that: 1) it can provide COM-style interfaces to a control; 2) it supports the minimum set of control container interfaces expected by an embedded control; and 3) it implements the minimum number of required functions in the aforementioned interfaces.

The complexity of control containers ranges from full development environments like Visual Basic 4, which allow controls to be integrated into the environment through the toolbar and implement design-mode and run-mode versions, to very simple control containers, like dialog boxes, implemented using the Microsoft Foundation Classes. This chapter discusses the characteristics of control containers and implementation of containers using the Microsoft Foundation Classes.

A Primer on Control/Container Interaction


Control and container interaction is based on the same foundation as the interaction of standard OLE document objects and their containers. Several of the interfaces required for control containers, such as IOleInPlaceSite and IOleInPlaceUIWindow, are used for interaction between OLE compound document objects and their containers. The standard interfaces are supplemented by several required interfaces designed specifically for control and container interaction, such as IOleClientSite and IOleControlSite. All of the required interfaces are listed later in this chapter, with detailed information about the purpose of the interface methods. There are also several optional container interfaces; containers may elect to implement or use them if they require the interface functionality.

One of the most important interfaces required for control/container interaction is IDispatch, the OLE automation dispatch interface. The control exposes an IDispatch interface that its container uses to access the control's properties and methods. The container also exposes an IDispatch interface to the control that allows the control to pass events to its container.

Each control embedded in a container is allocated a control site by the container. The container lays out the contained control, manages keyboard interaction, enables the properties of controls to be saved in some persistent format, handles events generated by controls, and exposes ambient properties to controls. Ambient properties allow OLE controls to retrieve information about the control site provided by their container.

Some containers wrap their controls with extended controls. Extended controls allow containers to maintain additional properties and events for controls without changing the mechanism through which they interact with the controls.

The next few sections describe in more detail the issues involved with creating controls, communicating with them, and wrapping them with extended controls.

Creating an OLE Control Instance


The control container can create a control by retrieving the control's IClassFactory interface through the standard OLE function CoGetClassObject(). The container can then use the ClassFactory::CreateInstance() function to create an instance of the OLE control. ClassFactory::CreateInstance() may return the error CLASS_E_NOTLICENSED if the control requires the container to specify control licensing information before it can be created.

The ClassFactory2 interface handles control licensing; it extends the ClassFactory interface with the GetLicInfo(), RequestLicKey(), and CreateInstanceLic() functions.


HRESULT ClassFactory2::GetLicInfo(LICINFO* pLicInfo);

GetLicInfo() enables the container to retrieve information regarding the licensing capabilities of the control. The function fills a LICINFO structure whose members describe the control's licensing information on the current machine:


typedef struct tagLICINFO

    {

    ULONG cbLicInfo;

    BOOL fRuntimeKeyAvail;

    BOOL fLicVerified;

    } LICINFO;

The LICINFO.cbLicInfo structure member simply specifies the size in bytes of the LICINFO structure. The LICINFO.fRuntimeKeyAvail member indicates whether or not the control can be instantiated on an unlicensed machine; if it is TRUE, the control can be created using a key obtained from ClassFactory2::RequestLicKey(). LICINFO.fLicVerified indicates whether or not a full machine license for the control exists.


HRESULT ClassFactory2::RequestLicKey(DWORD dwReserved, BSTR* pbstrKey);

RequestLicKey() allows a control container to create a runtime instance of a control on a machine that doesn't have a full license for the control (see the following example). If RequestLicKey() succeeds, the control's ClassFactory2 implementation will fill the pbstrKey with a string allocated using SysAllocString(). The container can pass the key to CreateInstanceLic(); the container is responsible for freeing the key using SysFreeString() after it is finished with it.


HRESULT ClassFactory2::CreateInstanceLic(IUnknown*

    pUnkOuter, IUnknown* pUnkReserved, REFIID riid,

    BSTR bstrKey, void** ppvObject);

CreateInstanceLic() uses the key obtained from the control through RequestLicKey() to create an instance of the control (see the following example). The pUnkOuter argument is a pointer to the controlling unknown for the control if the control is being aggregated; a control container that uses extended controls would pass its IUnknown implementation as an argument. pUnkReserved is a currently unused argument—it must be specified as NULL. The riid argument is the type of interface pointer requested by the container; a container may use the IID_IOleObject to retrieve the control's implementation of the IOleObject interface. The bstrKey is the key obtained using the ClassFactory2::RequestLicKey() function. Finally, the ppvObject is a pointer to the interface returned by the control.

Control/Container Communication Using IDispatch


The primary communication link between a container and a control is through the OLE automation IDispatch interface. IDispatch, the foundation of OLE automation, provides a generic means through which function calls may be passed to an object. For OLE controls, a control-provided IDispatch allows a container to access control properties and methods. Likewise, a container-provided IDispatch enables controls to access container ambient properties and alert the container of events.

Note


Communication between a control and container is pretty complex. The next few sections describe some of details of the communication, but I should forewarn you that the Microsoft Foundation Classes basically handle all of the details for you.


Fundamentals of IDispatch

The primary IDispatch method used for control/container communication is Invoke(). The prototype for Invoke() is:


HRESULT IDispatch::Invoke(DISPID dispidMember,

    REFIID riid, LCID lcid, unsigned short wFlags,

    DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult,

    EXECPINFO FAR* pexcepinfo, unsigned int FAR* puArgErr);

Callers of IDispatch::Invoke() use a Dispatch ID, or DISPID, to identify the OLE automation member that they want to access. The value of wFlags indicates whether a property or method is the target of Invoke(). Parameters to the property or method are passed to Invoke() as an array of variants, and the return value from the call is used to fill the pvarResult parameter.

Using IDispatch to Access Control Properties and Methods

Controls may contain a large number of properties and methods. As you know from the previous chapter, the capabilities of the control are defined in the control's type information, or type library. Control containers get access to the properties and methods using a control's IDispatch interface.

To get or set a property value, or to call a control method, you use the control's IDispatch::Invoke() implementation. You specify the specific property or method using its dispatch ID; you can determine the dispatch IDs statically by using a tool like the OLE viewer to examine the control's type library, or you can use the control's type information at runtime.

Later in the chapter, the examples use a control called, appropriately enough, SimpleControl. SimpleControl really doesn't do anything interesting; it exposes two properties, no methods, and two events. One of the properties is a SimpleName property with a DISPID of 2. SimpleName is a string; setting the value of SimpleName causes the control to fire an OnSimpleNameChange event. The following code can be used to change the SimpleName property:


VARIANT varArg;           // New property value

DISPPARAMS dispparams;    // Structure containing property parameter

DISPID dispidNamed;       // DISPID of named argument (just one here)

EXCEPINFO excepinfo;      // Contains exception information, should one occur

HRESULT hres;             // Result of property set

UINT uiArgErr;            // Index to erroneous parameter

// Setup new property value parameter

VariantInit(&varArg);

V_VT(&varArg) = VT_BSTR;

V_BSTR(&varArg) = SysAllocString(L"A new name");

// ...setup parameter information...

memset(&dispparams, 0, sizeof dispparams);

dispparams.rgvarg = &varArg;

dispparams.cArgs = 1;

dispidNamed = DISPID_PROPERTYPUT;

dispparams.rgdispidNamedArgs = &dispidNamed;

dispparams.cNamedArgs = 1;

// ...and initialize EXECPINFO structure

memset(&excepinfo, 0, sizeof EXCEPINFO);

// Call Invoke to set the property; assumes pDispatch is the

// interface pointer for SimpleControl's IDispatch

hres = pDispatch->Invoke(2, IID_NULL, 0, DISPATCH_PROPERTYPUT, &dispparams,

    NULL, &excepinfo, &uiArgErr);

if (FAILED(hres))

{

    // Handle error

}

else

{

    // Call was successful

}

VariantClear(&varArg);

The level of effort illustrated in the code snippet is representative of what's necessary to manipulate control properties or methods. You will probably notice that most of the work in manipulating a control's properties or methods is just setting up the parameters. The preceding example is a simple one; if you're dealing with named arguments for method calls, it can get more complex. For more information, you can check out the documentation for the IDispatch interface in the Win32 SDK help.

Using IDispatch to Get Control Events

Controls notify their containers of events using their pointer to the container's IDispatch interface. Before a control can do so, the container must connect its IDispatch to the control as an outgoing interface. To connect its IDispatch, the container must find the control's connection point for the interface.

Connection points are maintained by controls that fire events. The interface to the set of control points, or more precisely IConnectionPoint objects, is through the control's implementation of IConnectionPointContainer. The IConnectionPointContainer interface allows the container to enumerate the control's outgoing interfaces, or to find a specific outgoing interface using an interface ID or IID. For events, the container needs the IID of the control's IDispatch interface with the default and source attributes. You can get the IID statically, using the aforementioned OLE Viewer tool, or you can find it at runtime using the control's type information interfaces. Once the container has acquired the correct IConnectionPoint interface, it can attach its event IDispatch using the IConnectionPoint::Advise() method.

When a control fires an event, it passes the parameters through the standard IDispatch array mechanism. The control uses the DISPID in the call to IDispatch::Invoke() to indicate which event occurred.

Container Ambient Properties


Ambient properties are control container properties that are analogous to control properties. A container maintains ambient properties for all of its controls; typically, the container will expose the same values to all controls within a given frame. The OLE standards define a set of ambient properties with associated IDs that should be implemented by a container. The container also has means through which it can indicate to controls that the ambient properties have changed.

The implementation of control container properties is very similar to the implementation of control properties; container ambient properties are provided by an IDispatch interface that can be retrieved from the IOleControlSite interface that hosts the control. Controls get ambient property values in the same manner that containers get control properties; the container returns the value of the property in the return value from the IDispatch call.

The following list contains the standard ambient properties that may be provided by an OLE control container:

AutoClip
Type: VT_BOOL
Desc: This property indicates whether or not the container will perform automatic clipping for the control.

Some Ambient properties, such as UserMode, may apply to all of the controls within a control container. Other ambient properties, such as BackColor, may depend on the window to which the control is connected.

Extended Controls


Extended controls may be implemented by control containers to provide a wrapper for contained controls. Extended controls allow containers to associate whatever additional information or events they may need to maintain controls. Wrapping a control with an extended control simply involves performing standard COM aggregation. When a container creates a control instance, it passes in a controlling unknown that implements IDispatch for extended properties and events. If a request for a control property is received by the extended control, it passes the request through to the control's interface implementation.

The advantage of wrapping controls with extended controls is that the container can treat its properties and events and the control's properties and events without specialized code. Visual Basic is one of the best examples of a container that implements extended controls due to the seamless integration with its user interface.

Visual Basic and Extended Controls

Visual Basic uses extended controls extensively. Each control embedded in a Visual Basic Form has extended properties that store information such as a control's Top and Left coordinates within a form.

Visual Basic does an excellent job of integrating extended properties and events with standard control properties and events. Earlier in this chapter, you were introduced to the SimpleControl OLE control. SimpleControl has only two properties, SimpleName and SimpleName2, and two events, Click and OnSimpleNameChange. However, after you've added a SimpleControl to a VB form, a whole bunch of properties appear for the SimpleControl object. Figure 6.1 illustrates the properties box for the SimpleControl object in Visual Basic. Notice that SimpleControl's properties are integrated with the other Visual Basic extended properties. From the user's perspective, there is no difference between the properties maintained by the control and the properties maintained by Visual Basic.

Figure 6.1. The Visual Basic Property Editor.

Visual Basic also has a tool called the Object Browser. The Object Browser displays the methods and properties exposed by a Visual Basic control or by a referenced type. A user can add referenced types to Visual Basic using type libraries and Visual Basic's Tools|References… menu option. Figure 6.2 illustrates the display from the Object Browser when SimpleControl is added as a control to a Visual Basic project.

Figure 6.2. The Object Browser's view of SimpleControl as a control.

If SimpleControl is added to a Visual Basic project as a reference instead of a control, only the properties and methods that are part of SimpleControl are visible in the Object Browser. Figure 6.3 shows the Object Browser when SimpleControl is loaded as a reference.

Figure 6.3. The Object Browser's view of SimpleControl as a reference.

As indicated previously, Visual Basic also adds events to its controls. SimpleControl has only Click and OnSimpleNameChange events; Figure 6.4 illustrates SimpleControl's events and the events added by Visual Basic.

Figure 6.4. Visual Basic's Code Editor listing SimpleControl's events.

Unlike ambient property guidelines, there is no standard for the set of extended properties or events implemented by a control container. Visual Basic provides a good model for extended property integration; it includes properties that handle generic issues like storing control layout information (Top, Left, and so on), and it integrates those properties seamlessly with its user interface.

Required Container Interfaces


There are a substantial number of OLE Interfaces that must be implemented by an OLE control container. The following sections describe the interfaces and the expected behavior of the container upon receiving a method call on the interface.

Some of the methods are indicated as optional methods; the optional methods are not essential for the functioning of controls in a control container. Containers can return E_NOTIMPL or S_OK as appropriate for the optional methods.

IOleClientSite


Controls use IOleClientSite to obtain information about their container, including information that can be used to interact with other controls in the container.


HRESULT IOleClientSite::GetContainer(LPOLECONTAINER FAR* ppContainer);

The control uses this method to acquire a pointer to the container's IOleContainer interface. A control may use the IOleContainer interface to navigate to other controls contained within the control's container.


HRESULT IOleClientSite::ShowObject();

The control uses this method to ask its container to show it; this ensures that the control and container are visible:


HRESULT IOleClientSite::OnShowWindow(BOOL fShow);

The control calls this to notify its container when its window is about to become visible or invisible:


HRESULT IOleClientSite::SaveObject();(optional)

This method is typically used to save the object that is connected to a client site. The embedded object uses SaveObject() to request its container to save it to persistent storage; the object will probably call this method during the call to its IOleObject::Close() method.


HRESULT IOleClientSite::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker,


[icc]IMoniker **ppmk);(optional)

GetMoniker() is used by an embedded object to request a moniker, which is used to support OLE linking, from its container.


HRESULT IOleClientSite::RequestNewObjectLayout();(optional)

A loaded but inactive control uses RequestNewObjectLayout() to ask its container to allocate more or less space to display the control. In the implementation of RequestNewObjectLayout(), the container can query the control for the desired size by calling the control's GetExtent() method.

IAdviseSink

Control containers only need to implement IAdviseSink if they want to receive notifications of changes to controls that support IDataObject or IViewObject. Containers may also choose to implement this if they support insertion of controls as standard embedded OLE objects.


void IAdviseSink::OnDataChange(FORMATETC* pFormatetc, STGMEDIUM* pStgmed);

If a control supports IDataObject, it can notify its container of data changes through this method:


void IAdviseSink::OnViewChange(DWORD dwAspect, LONG lindex);

If a control supports IViewObject, it can call this container method to indicate that the view of the control has changed:


void IAdviseSink::OnRename(IMoniker *pmk);

This container member will be called when a control's moniker changes if the control supports linking:


void IAdviseSink::OnSave();

The control uses OnSave() to notify its container that it has been saved:


void IAdviseSink::OnClose();

Controls use OnClose() to indicate a transition from running to loaded state:

IOleInPlaceSite


This interface is used to manage interaction between a control container and a control's client site. The client site is the display site for the control; consequently, the interface is derived from the IOleWindow interface. IOleInPlaceSite provides methods that may be used to manage the activation and deactivation of a control, retrieve information about the position in the parent window where the control should place its in-place activation window, manage scrolling of the control, the control's undo state, and control borders.


HRESULT IOleWindow::GetWindow(HWND* phwind);

The control uses this to retrieve the handle to its in-place window:


HRESULT IOleWindow::ContextSensitiveHelp(BOOL fEnterMode);

A control can notify its container of a request for context-sensitive help by calling this container method:


HRESULT IOleInPlaceSite::CanInPlaceActivate();

This is used by the control to determine whether or not the container will allow it to active in place:


HRESULT IOleInPlaceSite::OnInPlaceActivate();

The control calls this to notify its container that it is in the process of becoming in-place active:


HRESULT IOleInPlaceSite::OnUIActivate();

The control uses this method to notify its container that it is about to be activated in place. In response and if appropriate, the container should remove whatever user interface is part of its own activation. If a different control is being deactivated as this control becomes active, the container should notify the other control of its state change using its UIDeactivate() method.


HRESULT IOleInPlaceSite::GetWindowContext(IOleInPlaceFrame** ppFrame,

[icc]IOleInPlaceUIWindow** ppDoc,LPRECT lprcPosRect, LPRECT lprcClipRect,

[icc]LPOLEINPLACEFRAMEINFO lpFrameInfo);

A control calls this on activation to retrieve pointers to the IOleInPlaceFrame and IOleInPlaceUIWindow interfaces provided by its container, the position and clipping rectangles for the control, and an OLEINPLACEFRAMEINFO structure listing accelerators supported by a container during an in-place session.


HRESULT IOleInPlaceSite::Scroll(SIZE scollExtent);

This is called by a control to request its container to scroll. After the container has finished scrolling, it should check if the control's visible rectangle has been affected. If it has been affected, the container should call the control's SetObjectRects(), on the IOleInPlaceObject interface, to give the control a new clipping rectangle.


HRESULT IOleInPlaceSite::OnUIDeactivate(BOOL fUndoable);

The control uses this to notify its container that it is deactivating its user interface components; the container should correspondingly reinstall its user interface. The fUndoable flag indicates whether or not the control can undo changes that occurred; to undo the changes, the container can call the control's OleInPlaceObject::ReactivateAndUndo() method.


HRESULT IOleInPlaceSite::OnInPlaceDeactivate();

Controls call the container's OnInPlaceDeactivate() method to indicate that they are fully deactivated. After a control has called this method, it is no longer possible for the container to undo changes.


HRESULT IOleInPlaceSite::DiscardUndoState();

This method is used by the control to indicate to its container that there is no longer any undo state; therefore, the container should not call the control's OleInPlaceObject::ReactivateAndUndo() method.


HRESULT IOleInPlaceSite::DeactivateAndUndo();

The control calls this if the user invokes undo immediately after activating it. In response, the container should call the control's IOleInPlaceObject::UIDeactivate() method to activate itself, remove the control's UI, and undo changes to the control's state.


HRESULT IOleInPlaceSite::OnPosRectChange(LPCRECT lprcPosRect);

This is called by a control to indicate a size change; the container should call the control's IOleInPlaceObject::SetObjectRects() to inform the control of the new size and position of the in-place window and new clipping rectangle.

IOleControlSite


Control containers implement this interface to communicate with embedded controls. The following methods are part of the IOleControlSite interface.


HRESULT IOleControlSite::OnControlInfoChanged(void);

The control calls this method to indicate to the container that the control's control information has changed. The control information is stored within the CONTROLINFO structure; the container can retrieve the updated information from the control using the GetControlInfo method on the control's IOleControl interface. The CONTROLINFO structure contains control keyboard accelerators and keyboard behavior flags.


HRESULT IOleControlSite::LockInPlaceActivate(BOOL fLock);

The control calls this to determine whether or not it should remain in-place active even if some type of deactivation event occurs:


HRESULT IOleControlSite::GetExtendedControl(IDispatch** ppDisp);

This is called by the control to obtain the IDispatch pointer to the extended control implemented by the container. Extended controls are used by the control container to maintain additional properties for a control, such as its X and Y location within the control container. Control containers like Visual Basic use extended controls to implement standard control properties like Top, Left, Height, and Width.


HRESULT IOleControlSite::TransformCoords(POINTL* pptlHimetric,

[icc]POINTF* pptfContainer, DWORD dwFlags);

The control uses this to convert OLE standard HIMETRIC units in a POINTL structure to the units preferred by the container in a POINTF structure. This method may also be used to do the reverse, that is, convert coordinates from control into standard HIMETRIC units.


HRESULT IOleControlSite::TranslateAccelerator(LPMSG pMsg, DWORD grfModifiers);

UI Active controls use this method to defer keystroke processing to their container. After a control becomes UI Active, the container transfers keystrokes to the control using the TranslateAccelerator() method on the control's IOleInPlaceActiveObject interface.


HRESULT IOleControlSite::OnFocus(BOOL fGotFocus);

This is called by the control to indicate to the container whether it has gained or lost input focus; the container can manage Default and Cancel button state accordingly:


HRESULT IOleControlSite::ShowPropertyFrame();

The control uses this method to request the container to display a property page frame for the control. The container can take the opportunity to create a property frame that includes pages for extended control properties; this ensures that both extended and standard control properties are maintained within a single, consistent user interface.

IOleInPlaceFrame


Controls that have associated frame-level tools, like toolbars or menu items, use this container interface to manage container user-interface changes on control activation. Containers can choose to implement optional methods based on their user-interface characteristics. For example, if a container has toolbars, it may choose to implement the toolbar-oriented negotiation functions like GetBorder() and RequestBorderSpace().


HRESULT IOleWindow::GetWindow(HWND* phwind);

The control uses this to retrieve the handle to its in-place window.


HRESULT IOleWindow::ContextSe


nsitiveHelp(BOOL fEnterMode);(optional)

A control can notify its container of a request for context-sensitive help by calling this container method:


HRESULT IOleInPlaceUIWindow::GetBorder(LPRECT lprcBorder);(optional)

Controls can use this container method to retrieve the outer rectangle, relative to the frame window, where the control can install its toolbar:


HRESULT IOleInPlaceUIWindow::RequestBorderSpace(LPCBORDERWIDTHS


[icc]pborderwidths);(optional)

A control calls RequestBorderSpace() with a rectangle indicating the desired space for a toolbar before attempting to install its toolbar UI. If the container accepts the request by returning S_OK, the control can call SetBorderSpace() to ask the container to allocate the requested space.


HRESULT IOleInPlaceUIWindow::SetBorderSpace(LPCBORDERWIDTHS


[icc]pborderwidths);(optional)

The container's SetBorderSpace() method is called when the control requests the allocation of space for the control's toolbar.


HRESULT IOleInPlaceUIWindow::SetActiveObject(IOleInPlaceActiveObject *pActiveObject,


[icc]LPCOLESTR pszObjName);

Controls call SetActiveObject() to establish a communication link to the container's frame window:


HRESULT IOleInPlaceFrame::InsertMenus(HMENU hmenuShared,

[icc]LPOLEMENUGROUPWIDTHS lpMenuWidths);(optional)

This method is called by controls to build up a composite menu containing the container's and control's menu items:


HRESULT IOleInPlaceFrame::SetMenu(HMENU hmenuShared, HOLEMENU holemenu,


[icc]HWND hwndActiveObject);(optional)

SetMenu() is called by controls to request the container to install a composite menu built up by previous calls to InsertMenus():


HRESULT IOleInPlaceFrame::RemoveMenus(HMENU hmenuShared);(optional)

The control calls this method to allow the container to remove its menu elements from the composite menu:


HRESULT IOleInPlaceFrame::SetStatusText(LPCOLESTR pszStatusText);(optional)

A control can call SetStatusText() to request the container to display status text from the control in the container's status line:


HRESULT IOleInPlaceFrame::EnableModeless(BOOL fEnable);(optional)

The control can call EnableModeless(FALSE) to ask its container to disable any modeless dialog boxes that it may be displaying. After the container has done so, the control may display its own modal dialog. When it is finished, the control should call EnableModeless(TRUE) to re-enable the container's modeless dialogs.


HRESULT IOleInPlaceFrame::TranslateAccelerator(LPMSG lpmsg, WORD wID);

This method is used to translate keystrokes intended for a container's frame window when a control is active in place.:

IOleContainer


Controls can use a container's implementation of IOleContainer to retrieve information about other controls in the container or to perform object linking functions.


RESULT IOleContainer::ParseDisplayName(IBindCtx* pbc, LPOLESTR pszDisplayName,

UNLONG* pchEaten, IMoniker** ppmkOut);(optional)

Controls that support linking use this to ask their container to parse a display name and create a moniker. Containers only need to implement this method if they support links to controls or other embedded objects.


HRESULT IOleContainer::LockContainer(BOOL fLock);(optional)

If a control and its container support linking, the control will call LockContainer(TRUE) to keep the container running until all link clients have been updated. After the clients have been updated, the control should call LockContainer(FALSE) to remove external locks on the container and allow the container to terminate.


HRESULT IOleContainer::EnumObjects(DWORD grfFlags, IEnumUnknown** ppenum);

A control can use this method to enumerate all of the controls and objects in its container. It's important to note that the enumerator may not actually return all visible controls in the container because some of them may be standard Windows controls.

Optional Container Interfaces


There are a number of container interfaces that are not required but that may be implemented or supported by containers that require their functionality. The following sections describe the interfaces and the expected behavior of the control or container upon receiving a method call on the interface.

ISimpleFrameSite


Containers can choose to implement support for the ISimpleFrameSite interface if they want to support controls that contain other controls. An example of a control that might make use of this interface is a group box that handles certain interaction characteristics of its contained controls. The purpose of this class is to allow controls to filter messages to controls that they contain while allowing them to defer messages for processing by the root control container.


HRESULT ISimpleFrameSite::PreMessageFilter(HWND hwnd,

    UINT msg, WPARAM wParam, LPARAM lParam,

    LRESULT* plResult, DWORD* pdwCookie);

This method gives a control the opportunity to process a message that is received by a contained control's window before the contained control does any processing:


HRESULT ISimpleFrameSite::PostMessageFilter(HWND hwnd,

    UINT msg, WPARAM wParam, LPARAM lParam,

    LRESULT* plResult, DWORD* pdwCookie);

A control can use this method to defer message processing to the control's container after the control and its contained control have had an opportunity to process the message:

IPropertyNotifySink


Containers implement the IPropertyNotifySink interface if they want to receive notifications about control property changes. This is useful, for example, if a container maintains its own property-editing user interface. Because this is an outgoing interface, the container must connect this to the control using the connection point mechanism.


HRESULT IPropertyNotifySink::OnChanged(DISPID dispid);

The control uses this method to notify the container that the property with the dispatch ID dispid has changed:


HRESULT IPropertyNotifySink::OnRequestEdit(DISPID dispid);

The control uses OnRequestEdit() to notify its container that one of its properties is going to change. The container can respond with S_OK or S_FALSE. The result S_OK allows the control to proceed with the change; S_FALSE indicates that the container won't allow the control to make the change.

IClassFactory2


Support for IClassFactory2, described earlier in this chapter in the section titled "Creating an OLE Control Instance," allows a control container to support runtime licensing. It is implemented by the control, not the control container. If the control implements it, the container may use it as an alternative to IClassFactory to instantiate a control.

Creating an MFC-Based Dialog Box Control Container


The AppWizard, Microsoft Foundation Classes, and the ClassWizard, all of which are integrated pieces of the Microsoft Developer's Studio, make it trivial to create a dialog box that is a very functional OLE control container.

To create a dialog-based control container, perform the following steps from the Microsoft Developer's Studio:

  1. 1. Create a new project named DlgContainer using the MFC AppWizard. The application type should be executable, or exe.
  2. 2. On the first wizard page, select Dialog Base" and click Next.
  3. 3. On the second wizard page, select the OLE Controls option.
  4. 4. Click Finish and OK to allow the AppWizard to create your project.
  5. 5. Go to the Resources tab in the project workspace, expand the list of dialog resources by double-clicking the Dialog folder, and double-click to edit the dialog resource with the IDD_DLGCONTAINER_DIALOG identifier.
  6. 6. Select Insert, Component from the menu bar. The Component Gallery dialog box will appear. Click on the tab marked "OLE Controls." Figure 6.5 shows the Component Gallery dialog.

Figure 6.5. The Component Gallery dialog.

  1. 7. Select a control to insert into the project. I've selected the "Simple Control", indicated with a red "thumbs-up" bitmap, that was generated using the MFC OLE Control classes. Click the Insert button to add the OLE control to your project.
  2. 8. After you have clicked the Insert button, a dialog box will appear that asks you to confirm the name of the control wrapper class that will automatically be generated. Figure 6.6 illustrates the Confirm Classes dialog. Select OK to accept the "CSimpleControl" class name. The component gallery will generate C++ header and source files for the CSimpleControl class. Select Close to return to the resource editor.

Figure 6.6. The Confirm Classes dialog.

Note


The "Simple Control" that is being used here only has simple BSTR-based properties; if a control had been inserted that supports fonts or pictures, two additional classes would be created—CFont and CPicture classes.

  1. 9. On the resource editor toolbar, you will notice an additional item, a red "thumb's up" that is identical to the control bitmap in the Component Gallery. Select the "thumb's up" and draw a "Simple Control" on the Dialog box.
  2. 10. Select the new control on the Dialog box, and right-click or press Alt+Enter to display the control properties. Figure 6.7 illustrates the resource editor display after showing the properties. The first page includes properties from the Developer's Studio, the second and third pages are directly provided by "Simple Control," and the last page contains all of the control properties presented by the Developer's Studio in list format.

Figure 6.7. The Resource Editor with SimpleControl's properties.

  1. 11. Save the modified dialog resource (File|Save from the menu), then press Control+W to display the Control Wizard.
  2. 12. Go the "Member Variables" tab in the Control Wizard, select the CDlgContainerDlg class, and then the IDC_SIMPLECONTROLCTRL1 control ID. Click the Add Variable button—a dialog box will appear prompting for the name of the variable. Enter m_simpctrl for the variable name; the variable category should already be control and the variable type should be CSimpleControl.

That's all that it takes to create an MFC project that includes a dialog box that contains an OLE control! It's truly remarkable considering the amount of effort that would be needed to support the same functionality if you implemented the equivalent dialog from scratch.

The Developer's Studio is doing some pretty interesting things here; first, to create the OLE Control page in the Component Gallery, it's scanning the registry to find controls, extracting their toolbar bitmaps, and finally adding them to what looks like a ListView control for user selection.

Second, the Developer's Studio is generating a C++ class that wraps the OLE control. Each of the properties of the control is exposed through Get/Set class methods; the OLE control methods are likewise exposed as class methods. Later in the chapter, you'll learn about some of the magic behind the implementation of the MFC classes that enable them to take standard C++ function calls and convert them in to IDispatch calls that OLE controls understand.

Third, event information from the control is scanned and included in the ClassWizard Message Maps tab. Figure 6.8 shows the ClassWizard with message maps for SimpleControl.

Figure 6.8. The ClassWizard with SimpleControl messages.

To handle the OnSimpleNameChange event fired by the SimpleControl included in the aforementioned example, all you need to do is select the IDC_SIMPLECONTROL1 object ID and the OnSimpleNameChange message, then click the Add Function button. The ClassWizard integrates window message handling and control event handling into the same easy-to-use user interface. That's very impressive when you consider that the method of retrieving standard window-type events is completely different from retrieving Event Sink-based OLE control messages.

Having read the descriptions of the interfaces required for OLE control containers, and the information about the interaction between control and container, you should be very impressed with the Microsoft Developer's Studio and MFC! The Developer's Studio integrates OLE controls directly into the development environment and alleviates literally all of the work required to implement a control container.

Creating a Non-Dialog Resource-Based MFC SDI Control Container


Creating a non-dialog resource-based MFC SDI Control Container is somewhat more complex than creating a dialog-resource-based control container. MFC includes several types of CView-derived classes. Some of them, like CFormView, use dialog resources to lay out controls. Others, like the base CView class, don't use dialog resources for layout. Adding controls to CView classes is more complex than adding controls to resource-based views because: 1) it requires that you create the control explicitly in code; and 2) it doesn't automatically integrate control events into the ClassWizard.

Luckily, creating the control in code is pretty simple. Unfortunately, handling control events involves significantly more manual code editing. The explanations will cover control events after discussing the basic details of creating a SDI control container.

Create the SDI Container Application


To create a SDI control container, perform the following steps from the Microsoft Developer's Studio:

  1. 1. Create a new project using the MFC AppWizard for executables; name the project "SDIContainer".
  2. 2. In step 1 of the AppWizard, select "Single document" for the type of application. Click Next twice to go to step 3 of the AppWizard.
  3. 3. In step 3 of the AppWizard, make sure that the check box next to "OLE controls" is checked. Click Finish to generate the application.
  4. 4. Select Insert|Component… from the Main Menu. The Component Gallery dialog box will launch. Select SimpleControl Control from the OLE Controls tab, then click Insert. Click Confirm in the class confirmation dialog, then click Close in the Component Gallery.

After completing these steps, you will notice that the ClassView tab of the project browser contains a new class—CSimpleControl. The Developer's Studio created this class in the same manner that the CSimpleControl class was created in the previous Dialog Box control container example.

Now the manual code editing begins; open SDIContainerView.h in the code editor and make the changes shown in bold in the following code snippet:


Excerpt from SDIContainerView.h -

// SDIContainerView.h : interface of the CSDIContainerView class

//

/////////////////////////////////////////////////////////////////////////////

#include "SimpleControl.h"

class CSDIContainerView : public CView

{

...

protected:

    // m_simpctrl is the member that will be used to

    // manipulate the OLE control

    CSimpleControl m_simpctrl;

// Generated message map functions

protected:

    //{{AFX_MSG(CSDIContainerView)

        // NOTE - the ClassWizard will add and remove member functions here.

        //    DO NOT EDIT what you see in these blocks of generated code !

    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()

    DECLARE_EVENTSINK_MAP()

};

...

You'll notice that the basic change is adding the SimpleControl.h header file to the view implementation header file, as well as a new member, m_simpctrl, to access and create an instance of the SimpleControl OLE control. You may not recognize the addition of the DECLARE_EVENTSINK_MAP() macro. That macro sets up the event sink for the view object; if you go back to the previous dialog box control container example, you'll notice that this was added without any intervention on your behalf.

Next, use the ClassWizard to create the CSDIContainerView::OnCreate function to handle the WM_CREATE message for the View class. In the body of CSDIContainerView::OnCreate, add code to create the SimpleControl OLE control instance. The required changes are marked in bold in the following code snippet:


Excerpt from SDIContainerView.cpp -

/////////////////////////////////////////////////////////////////////////////

// CSDIContainerView message handlers

int CSDIContainerView::OnCreate(LPCREATESTRUCT lpCreateStruct) 

{

    if (CView::OnCreate(lpCreateStruct) == -1)

        return -1;

    

    // TODO: Add your specialized creation code here

    m_simpctrl.Create(NULL, WS_VISIBLE, CRect(10, 10, 100, 100),

        this, ID_SIMPLECTRL);

    

    return 0;

}

The call to CSimpleControl::Create() initializes the OLE control and attaches it to the CView object (or specifically, in this case, the CSDIContainerView object which is derived from CView). One of the parameters to the call, ID_SIMPLECTRL, hasn't been defined yet. ID_SIMPLECTRL is an arbitrary identifier that will be associated with the SimpleControl instance; the identifier will be used when you make additions to the CSDIContainerView's Event Sink map. You're also missing the implementation of the Event Sink map, which you previously declared in the header using DECLARE_EVENTSINK_MAP(). To add the identifier and Event Sink map, make the following additions to SDIContainerView.cpp:


Excerpt from SDIContainerView.cpp -

// SDIContainerView.cpp : implementation of the CSDIContainerView class

//

#include "stdafx.h"

#include "SDIContainer.h"

#include "SDIContainerDoc.h"

#include "SDIContainerView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

#define ID_SIMPLECTRL    0

/////////////////////////////////////////////////////////////////////////////

// CSDIContainerView

IMPLEMENT_DYNCREATE(CSDIContainerView, CView)

BEGIN_MESSAGE_MAP(CSDIContainerView, CView)

...

END_MESSAGE_MAP()

BEGIN_EVENTSINK_MAP(CSDIContainerView, CView)

END_EVENTSINK_MAP()

Now you have a functional SDI OLE control container application that contains a single OLE control. Properties and methods of the control can be accessed using the member functions of the CSimpleControl class; the only major piece of functionality that's missing is the ability to get events from the control. You've already established the foundation for the event dispatch by including the necessary macro declarations in the CSDIContainer header and implementation files.

Add Event Handling


The SimpleControl OLE control only exposes two events—Click and OnSimpleNameChange. In the previous dialog box control container example, events from the SimpleControl control object were listed directly in the ClassWizard. Unfortunately, such is not the case for SDI or MDI MFC applications; you must add the code manually. As previously indicated, you've already added the necessary event sink macros. Now you need to add entries to the event sink map that correspond to the events you want to capture.

Events are added to the event sink using the ON_EVENT() macro. The details of the ON_EVENT() macro are described later in this chapter; for now, the explanation covers only what is necessary to handle the OnSimpleNameChange event.

To use the ON_EVENT() macro, you need to know the DISPID or member id of the event that you want to capture, and the types of parameters passed to the event. You can get that information using the OLE Object View utility (OLE2VW23.EXE), which can be launched from the Tools menu in the Developer's Studio. To get the parameters and id of the event, open the type information for the SimpleControl control by using the Object|View File|View Type|Library… menu option, and select SimpleControl.ocx from the File Open dialog box. Figure 6.9 illustrates the information from Object View.

Figure 6.9. SimpleControl type information in Object View.

The following is the function prototype reported by the Object Viewer for the OnSimpleNameChange event:


VOID OnSimpleNameChange(

     String OldName,

     PTR to String NewName

    )

Translated into a C++ prototype, with the function attribute required for MFC message handlers, that is:


afx_msg void OnSimpleNameChange(LPCTSTR OldName, BSTR FAR* NewName);

The dispatch ID, or DISPID, for the event is 1; it's listed in the Object Viewer as memid = 0x00000001.

Go ahead and add a member function that handles the event to the CSDIContainerView class. You'll call the member function OnSimpleNameChange() in accordance with the aforementioned prototype. The changes to the SDIContainerView.h and SDIContainerView.cpp are listed following:


Excerpt from SDIContainerView.h -

...

    DECLARE_MESSAGE_MAP()

    DECLARE_EVENTSINK_MAP()

    afx_msg void OnSimpleNameChange(LPCTSTR OldName, BSTR FAR* NewName);

};

...

Excerpt from SDIContainerView.cpp -

...

afx_msg void CSDIContainerView::OnSimpleNameChange(LPCTSTR OldName,

    BSTR FAR* NewName)

{

    AfxMessageBox("CSDIContainerView::OnSimpleNameChange");

}

...

Now you can add the ON_EVENT() entry to the EVENTSINK map. Add the following changes, marked in bold, to SDIContainerView.cpp:


...

BEGIN_EVENTSINK_MAP(CSDIContainerView, CView)

    ON_EVENT(CSDIContainerView, ID_SIMPLECTRL, 1, \

        OnSimpleNameChange, VTS_BSTR VTS_PBSTR)

END_EVENTSINK_MAP()

...

The arguments to the ON_EVENT() macro are pretty straightforward; the name of the class that gets the event, the ID of the control that generates the event (this was the ID that was used to create the control in CSDIContainerView::OnCreate()), the dispatch ID of the event, the name of the CSDIContainerView member function that is called when the event occurs, and the arguments to the event function encoded as strings. The reason why they are encoded as strings will be discussed later in the chapter.

Now test your newly implemented event sink. To do so, you need to write some code that causes the OLE control to fire the OnSimpleNameChange event. Go to the resource tab on the project workspace and edit the main menu, IDR_MAINFRAME. Add a separator after Paste on the Edit menu, and then add a menu item called Change SimpleName, with C as the hot key. Change the ID of the option to IDM_CHANGE_SIMPLENAME (you can do this in the Menu Item Properties dialog). Next, add a message handler to CSDIContainerView using the ClassWizard; the default name for the menu message handler (assuming that you correctly chose the Object ID IDM_CHANGE_SIMPLENAME and the COMMAND message) will be OnSimpleNameChange. Change it so that it is OnMenuSimpleNameChange—then click the Edit Code button.

Make sure that your implementation of the message handler for IDM_CHANGE_SIMPLENAME is as follows:


void CSDIContainerView::OnMenuChangeSimpleName() 

{

    m_simpctrl.SetSimpleName("ANewName");

}

Setting the SimpleName property of the SimpleControl instance causes the OnSimpleNameChange() event to fire; MFC ensures that the CSDIContainerView::OnSimpleNameChange() function is called to handle it.

Details of MFC Control Container Implementation


The Microsoft Foundation Classes do some very interesting things to ease the use of controls in a control container. As previously indicated, the Microsoft Developer Studio contains integrated tools that automatically generate classes to wrap the complexity of the control/container interface. Two of the most interesting and enlightening examples of MFC's control container code are evident in the MFC implementation of property get/sets and the implementation of event handling.

MFC Implementation of Property Get/Sets

The Microsoft Foundation Classes use a unique and intricate, but easy-to-use, mechanism to implement property Get/Set methods for controls embedded in a container. The foundation (no pun intended!) of the mechanism is the COleDispatchDriver class. COleDispatchDriver contains a public function named InvokeHelperV that is used to translate standard C++ style function calls into Dispatch type calls.

There are some interesting problems that MFC tackles here—the arguments to the property Get/Set method are on the stack, which is the convention for C++ function calls. The parameters are also standard C/C++ data types, such as longs. Furthermore, the fact that exposed C++ member functions can be called as standard C++ member functions is remarkable if you consider that the target for the function call is an OLE control, which uses OLE automation as the means for property and method calls.

C++ format function calls are not, of course, what the IDispatch::Invoke() function expects. The convention for calls using IDispatch::Invoke involves passing parameters as variants in a parameter array to a function called using a DISPID or dispatch id.

To demonstrate the MFC answer to the thorny problem of mapping C++ style calls to IDispatch::Invoke() calls, I created a dialog-based MFC application using the App Wizard. I then added a grid control to the main dialog using the component browser; after saving the modified dialog resource, I used ClassWizard to create a member variable to wrap the Grid in the main dialog class. In the process of creating the member variable, the ClassWizard scanned the OLE type information from the control, and generated gridctrl.cpp and gridctrl.h files.

The gridctrl.h Header File

The gridctrl.h header file contains the definition for a CGridCtrl class, derived from CWnd, that implements the properties and methods exposed by the control as C++ class member functions. An excerpt of the class definition from gridctrl.h follows:


...

class CGridCtrl : public CWnd

{

protected:

    DECLARE_DYNCREATE(CGridCtrl)

public:

    CLSID const& GetClsid()

    {

        static CLSID const clsid = { 0xa8c3b720, 0xb5a, 0x101b,

            { 0xb2, 0x2e, 0x0, 0xaa, 0x0, 0x37, 0xb2, 0xfc } };

        return clsid;

    }

...

// Attributes

public:

...

    short GetRows();

    void SetRows(short);

    short GetCols();

    void SetCols(short);

...

// Operations

public:

    void AboutBox();

    long GetRowHeight(short Index);

    void SetRowHeight(short Index, long nNewValue);

    long GetColWidth(short Index);

    void SetColWidth(short Index, long nNewValue);

...

};

The class contains the CLSID of the wrapped control, and a function to access the CLSID, as well as function declarations that correspond to properties and methods exposed by the control.

The GetRows and SetRows functions provide functionality that would be provided inside a container like Visual Basic using the standard assignment operator. In Visual Basic, ctrl.Rows = 10 would therefore correspond to the C++ function call ctrl.SetRows(10).

The interesting part of this equation—the implementation of the property Get/Set functions—is in the automatically generated gridctrl.cpp file.

The gridctrl.cpp Implementation File

The following excerpt from the gridctrl.cpp file illustrates the implementation of some of the aforementioned property Get/Set methods and general method calls:


// CGridCtrl

...

/////////////////////////////////////////////////////////////////////////////

// CGridCtrl properties

...

short CGridCtrl::GetRows()

{

    short result;

    GetProperty(0x8, VT_I2, (void*)&result);

    return result;

}

void CGridCtrl::SetRows(short propVal)

{

    SetProperty(0x8, VT_I2, propVal);

}

short CGridCtrl::GetCols()

{

    short result;

    GetProperty(0x9, VT_I2, (void*)&result);

    return result;

}

void CGridCtrl::SetCols(short propVal)

{

    SetProperty(0x9, VT_I2, propVal);

}

...

/////////////////////////////////////////////////////////////////////////////

// CGridCtrl operations

...

long CGridCtrl::GetRowHeight(short Index)

{

    long result;

    static BYTE parms[] =

        VTS_I2;

    InvokeHelper(0x1f, DISPATCH_PROPERTYGET, VT_I4, (void*)&result, parms,

        Index);

    return result;

}

void CGridCtrl::SetRowHeight(short Index, long nNewValue)

{

    static BYTE parms[] =

        VTS_I2 VTS_I4;

    InvokeHelper(0x1f, DISPATCH_PROPERTYPUT, VT_EMPTY, NULL, parms,

         Index, nNewValue);

}

long CGridCtrl::GetColWidth(short Index)

{

    long result;

    static BYTE parms[] =

        VTS_I2;

    InvokeHelper(0x20, DISPATCH_PROPERTYGET, VT_I4, (void*)&result, parms,

        Index);

    return result;

}

void CGridCtrl::SetColWidth(short Index, long nNewValue)

{

    static BYTE parms[] =

        VTS_I2 VTS_I4;

    InvokeHelper(0x20, DISPATCH_PROPERTYPUT, VT_EMPTY, NULL, parms,

         Index, nNewValue);

}

...

The manifest constants in the parms[] arrays are expanded to strings during precompilation. The output from the precompiler for CGridCtrl::GetRowHeight follows:


long CGridCtrl::GetRowHeight(short Index)

{

    long result;

    static BYTE parms[] =

        "\x02";

    InvokeHelper(0x1f, 0x2, VT_I4, (void*)&result, parms,

        Index);

    return result;

}

The precompiled output is interesting because it makes the definition of the parms[] array somewhat more clear. It's also interesting because it shows how the mapping from C++ function name to the DISPID required by IDispatch::Invoke occurs; the ClassWizard fills the first parameter to InvokeHelper with the DISPID, in this case the constant 0x1f, of the corresponding GetRowHeight IDispatch-exposed method.

From the definition of the Get/SetRowHeight functions, it's clear that MFC is using the InvokeHelper function to somehow translate the C++ function calls to IDispatch::Invoke() calls.

The InvokeHelper Function

The CGridCtrl's reference to the InvokeHelper() function is a reference to the CWnd::InvokeHelper() function. In turn, CWnd::InvokeHelper() is little more than a wrapper for the COleControlSite::InvokeHelper() function, which is pretty trivial; it sets up a variable argument list and forwards the function call to the COleControlSite::InvokeHelperV() function. Unsurprisingly, the COleControlSite::InvokeHelperV() call still isn't the end of the road; MFC aficionados will attest to the fact that the path of execution through MFC code can be very complex. COleControlSite::InvokeHelperV() ensures that an IDispatch interface pointer has been retrieved for the control object, wraps the pointer using an instance of COleDispatchDriver, and then, finally, calls COleDispatchDriver::InvokeHelperV() to actually make the IDispatch call.

The implementation of the COleDispatchDriver::InvokeHelperV() is very interesting. Each element of the variable argument list, prepared by InvokeHelper(), is converted to a variant and inserted in an instance of the DISPPARAMS structure, which is expected by the standard IDispatch::Invoke() method. During the iteration through the variable argument list, the aforementioned parms[] array is used to set the correct type of the variant and to calculate the correct amount to increment the list pointer using the va_arg() macro.

After the parameter conversion is complete, IDispatch::Invoke() is called using the IDispatch control interface pointer maintained by the COleDispatchDriver class. When Invoke() returns, MFC deallocates temporary memory used for the DISPARAMS structure, checks the return code for the Invoke call, and, if an error occurred during the call, throws a COleDispatchException containing all of the available information from the OLE exception structure.

It should be obvious at this point that the amount of work that MFC shields the casual control container from is just amazing! If MFC weren't used to implement the container, each property call would need to be implemented using the standard IDispatch mechanism, which is tedious at best from C++.

The MFC Implementation of Control Event Handling

Speaking from the perspective of the Microsoft Foundation Classes, control event handling is more or less the opposite of using a control's properties and methods. To use a control's properties and methods, MFC does the work to translate a C++ function call into a call to IDispatch::Invoke(). When handling events, MFC has to take a IDispatch::Invoke() call from a control connected to a container's IDispatch event sink, and convert it into a call to a container's C++ member function.

The Framework for Event Handlers

Event handlers are declared within a CCmdTarget-derived class using the DECLARE_EVENTSINK_MAP() macro. DECLARE_EVENTSINK_MAP() establishes the existence of a table that contains information that MFC requires to dispatch events. It must be supplemented by BEGIN_EVENTSINK_MAP(), ON_EVENT(), and END_EVENTSINK_MAP() macros in the class implementation file.

Earlier, you saw an example where I added the code necessary to instantiate an OLE control in a non-resource-based MFC view class. After adding code to create the control, I added event handling to handle the SimpleControl's OnSimpleNameChange event. Recall the following code snippet:


BEGIN_EVENTSINK_MAP(CSDIContainerView, CView)

    ON_EVENT(CSDIContainerView, ID_SIMPLECTRL, 1, \

        OnSimpleNameChange, VTS_BSTR VTS_PBSTR)

END_EVENTSINK_MAP()

ON_EVENT() is the macro that establishes an entry in a table of event handlers. When dispatching events, MFC will scan this table to find a function that matches an incoming event. The definition of ON_EVENT(), from AfxDisp.h, follows:


#define ON_EVENT(theClass, id, dispid, pfnHandler, vtsParams) \

    { _T(""), dispid, vtsParams, VT_BOOL, \

        (AFX_PMSG)(void (theClass::*)(void))pfnHandler, (AFX_PMSG)0, 0, \

        afxDispCustom, id, (UINT)-1 }, \

It contains all of the information that is necessary for MFC to map an IDispatch::Invoke() call to a C++ class function call. As with the aforementioned InvokeHelper() function, used for property set/get calls, the event parameters are encoded as a string within ON_EVENT() macro use.

MFC Event "Forwarding"

When MFC receives an event from a contained control, MFC attempts to find an entry in the aforementioned event sink map. If MFC finds an entry that matches the event dispatch ID, it checks the parameters in the string parameter signature recorded with the entry. After doing so, it performs an intricate conversion of a DISPPARAM array from an array to arguments on the stack, suitable to be passed to a C++ member function.

The MFC "Event Forwarding" mechanism is one of the most useful elements of the class library; like the MFC property implementation, it shelters the container implementer from the details of managing parameters from IDispatch by converting calls to standard C++ function invocations.

Future Directions with OLE Control Containers


Microsoft's ActiveX internet strategy plays heavily on existing technology such as COM and OLE or ActiveX controls. As you learned from the previous chapter, OLE controls require a large number of interfaces to be implemented. Lots of implementation generally means large controls, and large controls are a problem when users have low bandwidth modem connections. Part of the goal of ActiveX is to simplify the requirements for controls; as the requirements change, it's quite conceivable that the requirements for control containers will also change.

I have no doubt that Microsoft will continually update the Microsoft Foundation Classes to reflect the changing standards for OLE or ActiveX controls. When implementing a control container in the future, it's a safe bet to say that you will probably want to use the Microsoft Foundation Classes, unless you have specific requirements that aren't met by MFC.

Summary


This chapter has presented some of the issues involved with the creation of OLE control containers. OLE controls use literally all of the various OLE technologies; consequently, they are very complex to implement. As you can tell from the number of required interfaces for OLE control containers, it is also very complex to implement a control container.

The Microsoft Foundation Classes radically decrease the amount of code necessary to generate a control container. As a matter of fact, implementing a dialog-resource-based control container is trivial! It's basically a point-and-click affair; as the developer, you have to worry about the logic, not the infrastructure, when you use MFC for control containers.

Previous Page Page Top TOC Next Page