The Polyline component that we've been creating in this book is the closest thing we have already to an OLE control: it is in-place capable, supports outgoing interfaces, and even has a custom interface that exposes its specific properties. It requires only a few specific additions, primarily involving additional interfaces. As with Patron, this chapter's Polyline sample (CHAP24\POLYLINE) should not be considered a complete control. I'm providing it merely to illustrate how some of the control mechanisms appear in code. I'll leave it to the development tools to create truly complete implementations.
So what are Polyline's limitations? First of all, it's not a terribly useful control—it is much better suited to being a content object in a compound document. Nevertheless, we can use it to demonstrate control-like behavior. Second, Polyline has no accelerators and no mnemonics, so it doesn't demonstrate those aspects of control development. Third, none of its properties are bindable, so Polyline doesn't implement IPropertyNotifySink as an outgoing interface. Fourth, it doesn't support Save As Text. And finally, Polyline is not marked for self-registration as shipping controls should be—you'll need to use its REG file to properly register it. This REG file includes the Control key but does not include a ToolboxBitmap entry or mark Polyline with any of the control-specific MiscStatus flags.
These limitations do not mean that Polyline demonstrates nothing of interest. It already supports the connection point mechanism for outgoing interfaces, for example, which we added in Chapter 5. Polyline also demonstrates the handling of both storage-based and stream-based persistence, a feature we added in Chapter 8. Of course, it already implements IDataObject, IViewObject2, IOleObject, IOleInPlaceObject, IOleInPlaceActiveObject, IRunnableObject, IExternalConnection, and IOleCache2. To Polyline's existing implementation, I've added the features listed on the following pages; implementation of the additional interfaces can be found in CONTROL.CPP.
Polyline now supports OLE Automation through a dispinterface named DIPolylineControl. This interface exposes the properties BackColor, LineColor, and LineStyle, with the custom methods Clear and RemoveLastPoint. These properties and methods reflect the relevant parts of the custom interface IPolyline10, which Polyline has carried throughout much of this book. This dispinterface is implemented using a custom interface, IPolylineControl, which is now another custom interface alongside IPolyline10. The addition of automation support, of course, means that Polyline also has type information. This is described in POLYLINE.ODL and exposed through the addition of IProvideClassInfo.
Polyline has always supported a custom outgoing interface named IPolylineAdviseSink10, with members such as OnPointChange and OnColorChange. For the purposes of becoming a control, a dispatch version of this interface, DIPolylineAdviseSink10, is defined in POLYLINE.ODL and is marked as the default source interface for the control. This interface is Polyline's primary event set. IPolylineAdviseSink10 is itself marked as a source interface.
Polyline implements ISpecifyPropertyPages through which it returns only CLSID_PolylinePropPage (INC\BOOKGUID.H). This property page, shown in Figure 24-5, is implemented in CHAP24\POLYPROP and is structurally identical to the Beeper property page sample from Chapter 16. PolyProp simply uses a different set of controls and communicates with a different object. In particular, the property page applies changes through Polyline's custom interface, IPolylineControl. (This interface is also used to implement Polyline's incoming dispinterface through ITypeInfo::Invoke.)
Figure 24-5.
Polyline's property page.
Polyline now supports a Properties verb in IOleObject::DoVerb (IOLEOBJ.CPP), which first attempts to call IOleControlSite::ShowPropertyFrame. Failing that, Polyline displays its own property page by calling OleCreatePropertyFrame. Polyline supports not only OLEIVERB_PROPERTIES but also its own custom verb with the text "Properties…". It does this because the standard Properties verb has a negative value and will therefore not show up on verb menus by design.
Polyline implements IOleControl to round out its set of interfaces. Polyline has no accelerators, so OnMnemonic does nothing and GetControlInfo returns a structure containing no accelerators at all. However, Polyline does support event freezing and does reload ambient properties in OnAmbientPropertyChange.
On creation, Polyline retrieves the ambient properties BackColor and ForeColor (which are applied to the line color) along with UIDead and ShowHatching. Polyline's implementation of IOleControl::OnAmbientPropertyChange will reload UIDead and ShowHatching when they change. The function CPolyline::AmbientGet is simply a wrapper for IDispatch::Invoke(…, DISPATCH_PROPERTYGET,…), which is called on the container's ambient properties dispatch interface. The function CPolyline::AmbientsInitialize retrieves ambient properties and updates CPolyline variables as necessary. This latter function is called from within IOleObject::SetClientSite—in which the control first becomes aware of its container—and from within IOleControl::OnAmbientPropertyChange when it is called with DISPID_UNKNOWN.
Polyline supports the UIDead ambient property by ignoring WM_LBUTTONDOWN and WM_COMMAND messages that come into PolyWndProc (POLYWIN.CPP).
To support the ShowHatching ambient property, Polyline tells its hatch window, CHatchWin (implemented in CLASSLIB), to show or hide its hatching by calling CHatchWin::ShowHatch. To hide hatching, the hatch window resizes itself so that it is the same exact size of the Polyline window it contains. This effectively hides the hatching. When told to show hatching again, the hatch window resizes itself to be slightly larger than the Polyline window within it, effectively making that hatch border visible once again.
Polyline implemented its outgoing dispinterface for events by playing a little trick on the rest of its code. In a number of places in its source code, Polyline is written to send notifications through its custom interface IPolylineAdviseSink10. Since Chapter 5, this interface was the only one supported through IConnectionPointContainer. In making Polyline an OLE control, I had to add support for another outgoing interface that would call the container's IDispatch::Invoke. To do this, I added a class, CAdviseRouter (CONTROL.CPP), that implements the custom interface IPolylineAdviseSink10. When members of that interface are called, CAdviseRouter turns around and calls IDispatch::Invoke. So when a control container asks to hook up its own event sink to Polyline, we simply give that sink's IDispatch pointer to an instance of CAdviseRouter and install that object as the sink that the rest of Polyline's code will notify. Not only was I able to make this change without modifying most of Polyline's existing code, but I also had a central place (CAdviseRouter::Invoke) to detect whether events were frozen (as flagged in the variable CPolyline::m_fFreezeEvents). If events are frozen, this notification router object will not bother to call the container, discarding the event instead.
We have seen almost all of the other features in one capacity or another in previous chapters, so there is little else for us to discuss. Polyline illustrates how an OLE control really brings many of the separate OLE technologies into one coherent package. An OLE control is the combination of a COM object with incoming and outgoing interfaces, a persistent object with multiple persistence models, a data object, a viewable object (that supports caching), an automation object with type information, an object with property pages, an embeddable in-place–capable content object, and an implementation of IOleControl to round out the feature set.
Technically speaking, an OLE control is rather complex. With all those interfaces and the protocols that a control must obey, you might think that controls would be fat and slow. On the contrary, Microsoft's engineers have found that an OLE control will consistently outperform a similar control written using straight Windows SDK techniques or using the VBX standard. The primary reason is that interface function calls, especially between a container and an in-process control, are almost always faster than sending or posting messages to a window. This really makes a difference in the core operations of a control: sending events, manipulating properties, and receiving method calls from a container. All of these operations have used Windows messages in the past. With OLE Controls, these operations are accomplished through IDispatch::Invoke, which is faster overall. Certainly, other aspects of OLE Controls are slower—creation, for example—but what matters most is run-time performance once the control is loaded.
These performance gains, combined with the benefits of multiple interfaces, licensing, self-registration, object-controlled persistence, and all of the other features we've seen, promises to make OLE Controls the control standard for the future. Microsoft is hedging its bets on this technology in many areas of its business, and as time goes by we'll see that OLE Controls will become even more powerful, more flexible, and more pervasive. It is a technology to watch, along with the rest of OLE.