Notes on Patron as a Partial OLE Control Container
As an in-place–capable container for OLE Documents, the Patron sample from Chapter 22 is already well on its way to becoming an OLE control container. For this chapter's sample (CHAP24\PATRON), we'll try adding some control-related features to demonstrate a number of the concepts we've been describing. As elsewhere in our work with OLE Documents, implementing container features involves a good deal of user interface. Supporting OLE Controls is no exception.
Let me state again that Patron is not a complete control container by any stretch of the imagination. Patron merely demonstrates some of the techniques involved while leaving others unsupported. This sample doesn't implement any Tab key handling or tab order support, nor does it implement ISimpleFrameSite. It also does not handle default and cancel buttons, exclusive buttons, or labels. It doesn't have any way to call a control's methods or to manipulate its properties. Patron does not implement IPropertyNotifySink, demonstrate extended controls, provide a user interface for self-registering controls, use a control's toolbox bitmap, or deal at all with licensing issues. And finally, Patron ignores OLEMISC_SETCLIENTSITEFIRST and works only with controls that implement IPersistStorage and those marked "Insertable."
This last point deserves some clarification—Patron doesn't supply its own Insert Control dialog box, which normally would be populated with those objects marked with "Control" in the registry. Instead, it relies on the Insert Object dialog box we've used since Chapter 17. Therefore, Patron works only with controls marked "Insertable" and those that use the storage-based persistence model. If you'd like to play around with a control in Patron, you'll have to manually add the Insertable key and see what happens.
So does Patron do anything? What's left to demonstrate? Well, we still have a number of important pieces of a container—event handling, ambient properties, and keyboard mnemonics. The following list describes those parts of the OLE Controls technology that are demonstrated in this sample:
- Patron supplies the ambient properties UserMode, UIDead, SupportsMnemonics, Font, BackColor, ForeColor, LocaleID, ShowGrabHandles, and ShowHatching. The site's implementation of IDispatch is found in AMBIENTS.CPP. The interface does little more than copy values stored in Patron's CTenant class to the VARIANT out-parameters. The standard font object (14-point Arial) to return for the Font ambient property is managed in the CPages class, which creates the font inside CPages::Init with a call to OleCreateFontIndirect.
- Patron provides menu items for switching between design mode and run mode, toggling the UserMode ambient property. In design mode, Patron deactivates all the controls and will return S_FALSE from IOleInPlaceSite::CanInPlaceActivate. In this mode, Patron supplies its own grab handles for sizing controls and its own drag-and-drop pick region for moving controls around in the document. Various classes in Patron, such as CPatronDoc, CPages, and CTenant, maintain duplicate flags named m_fDesignMode for the purpose of knowing which mode is in use. In the tenant, for example, this flag will suppress the initial activation of any object marked OLEMISC_ACTIVATEWHENVISIBLE. When you switch to run mode, Patron will reactivate any such control and activate the UI of the currently selected one. When you create a new control in design mode, Patron does not initially activate it as usual for an embedded object because most controls have no open active state.
- Patron also provides menu items for toggling the UIDead, ShowGrabHandles, and ShowHatching ambient properties—you can watch what effect they have on various controls. The latter two properties are toggled together. All changes to ambient properties do, of course, generate a call to IOleControl::OnAmbientPropertyChange, which is centralized in the function CTenant::AmbientChange.
- Patron's site object, implemented in the class CTenant, implements IOleControlSite (ICONSITE.CPP) and the IDispatch interface for ambient properties (AMBIENTS.CPP). Inside IOleControlSite, Patron implements the members OnControlInfoChanged, LockInPlaceActive, and TransformCoords. Having no extended controls, we have no need to implement GetExtendedControl and ShowPropertyFrame. Having no keyboard support—nor support for default and cancel buttons—allows us to return E_NOTIMPL from OnFocus and TranslateAccelerator.
- The implementation of IOleControlSite::LockInPlaceActive simply increments or decrements a lock count. This affects CTenant::DeactivateInPlaceObject, which will not deactivate a control when the lock count is nonzero. Instead, it sets a flag in CTenant named m_fPendingDeactivate. Whenever IOleControlSite::LockInPlaceActive decrements the lock count to 0, it will call CTenant::DeactivateIn-PlaceObject if m_fPendingDeactivate is set.
- When Patron detects WM_KEYDOWN or WM_SYSKEYDOWN for Alt or Ctrl key combinations, it attempts to match a control's mnemonic in order to call IOleControl::OnMnemonic. This ends up in CPage::TryMnemonic, which loops through all controls on a page (using the current z-order), calling CTenant::TryMnemonic. This latter function will then scan the accelerator table in the control's CONTROLINFO structure as returned from IOleControl::GetControlInfo. If a match is found, we call IOleControl::OnMnemonic. Patron loads an object's CONTROLINFO structure initially inside CTenant::ControlInitialize, replacing it whenever IOleControlSite::OnControlInfoChanged is called.
- Patron will call OleRun for any control marked with OLEMISC_ALWAYSRUN. (See CTenant::ObjectInitialize.) Furthermore, code in CTenant::Draw and CTenant::Select suppresses drawing anything for a given control at run time when that control is marked OLEMISC_INVISIBLEATRUNTIME. The tenant code will also avoid UI activation of controls marked OLEMISC_NOUIACTIVE.
- Patron implements IOleClientSite::RequestNewObjectLayout to retrieve the control's new size. It passes the size to CTenant::UpdateInPlaceObjectRects. This latter function generates the clipping rectangle and calls IOleInPlaceObject::SetObjectRects.
- Patron allows you to assign a system sound to any control event through the Control Events dialog box shown in Figure 24-4. The Edit Events menu item is enabled whenever a control has events, which is determined through its type information. (This item is also placed on the right mouse button pop-up menu.) Although the actions you can assign to any event are limited, Patron does demonstrate how to handle a generic event set and how to save and load the action assignments persistently—with the limitation that it does not process event-specific arguments.
Patron best demonstrates the handling of an arbitrary event set. To display the dialog shown in Figure 24-4 (whose template is in EVENTS.DLG, by the way), we need some list of events and also to maintain a mapping from each event dispID to an action. We also need to implement an IDispatch that works with any event set to execute the action assigned to whatever event comes into IDispatch::Invoke. To receive these events in the first place, we have to find the type information for the event set. The following sections describe how Patron performs these steps.
Figure 24-4.
Patron's Control Events dialog box in which the user can assign actions (system sounds) to individual events. The events shown are those of the Polyline control implemented in this chapter.