Write ActiveX Controls
Using Custom Interfaces
Provided by ATL 3.0|
Brent Rector and Chris Sells
|An ActiveX control incorporates a great deal of COM functionality. For example, a control is a COM object. Therefore, an ATL control contains all the standard functionality of an ATL-based COM object. A control also has thread affinity and should live in a single-threaded apartment.|
This article assumes you're familiar with C++, ATL, and COM|
Code for this article: ATL30.exe (176KB)
This article is adapted from the forthcoming book, ATL Internals,
by Brent Rector and Chris Sells (Addison-Wesley, 1999).
Brent is the founder of Wise Owl Consulting Inc., a COM consulting firm. Reach Brent at http://www.wiseowl.com/brent.htm. Chris is an independent consultant specializing in designing
and building distributed systems using COM. Chris can be
reached at http://www.sellsbrothers.com.|
ATL 3.0 contains a number of enhancements
you can use when writing ActiveX® controls. In many cases, ATL takes care of all the details. You simply write the functionality specific to your control. Sometimes you need to enhance the functionality ATL provides before your control interoperates well with common containers such as Visual Basic®. In a few cases, ATL either doesn't do the right thing or gets it wrong, so we'll show you how to make it right.
A complete review of the COM interfaces and interactions between an ActiveX control and a control container is outside the scope of this article. If you are unfamiliar with the various interfaces and interactions described in this article, there are texts that specifically address these topics. Inside OLE, by Kraig Brockschmidt, is the original COM text and devotes hundreds of pages to in-place activation and visual interface components.
An ActiveX control is a superset of an in-place activated object, so you'll also need to read the OLE Controls specification from Microsoft, which describes the requirements of a control. In addition, the OLE Controls 1996 specification (commonly referred to as OC96) documents optimizations for control activations (such as windowless controls and windowless control containment), two-pass rendering for non-rectangular windows, hit testing for non-rectangular windows, fast activation protocols between controls and containers, as well as numerous other features. So rather than rewording the material available in these references, we're going to show you how to implement such an object.
ActiveX Control Functionality
A control incorporates a great deal of COM functionality. For example, a control is a COM object. Therefore, an ATL control contains all the standard functionality of an ATL-based COM object. A control is also a user interface component, so it has thread affinity and should live in a single-threaded apartment. In ATL, a control derives
from the CComObjectRootEx <CComSingleThreadModel> base class.
A control must be a createable class so that its container can instantiate it. Therefore, the control class will also derive from CComCoClass. Many controls use the CComCoClass default class object's implementation of the IClassFactory interface. Licensed controls override this default by specifying the DECLARE_CLASSFACTORY2 macro, which declares a class object that implements the IClassFactory2 interface.
In addition, most controls will support one or more of the following features:
® Internet Explorer prefer this medium. Some controls also support IPersistStorage so they can be embedded into OLE documents.
Fast and efficient windowless activation as provided by the IOleInPlaceObjectWindowless interface when the control's container supports this optimization.
Fast and efficient exchange of multiple interfaces during activation between a control and its controls using the IQuickActivate interface.
Object safety settings either through component category membership or IObjectSafety.
Drag and drop support as provided by implementations of the IDataObject, IDropSource, and IDropTarget interfaces.
- Stock properties and methods such as ForeColor and Refresh that a container can access via the control's IDispatch implementation.
- Custom properties and methods that a container can access via the control's IDispatch implementation.
- Stock and custom event callback methods using the connection points protocol to a container's dispinterface implementation. This requires the control to implement the IConnectionPointContainer and IProvideClassInfo2 interfaces as well as a connection point that makes calls to the event dispinterface.
- Property change notifications to one or more clients using the connection points protocol to the clients' IPropertyNotifySink interface implementations. Control properties that send such change notifications should be marked in the control's type library using the bindable and/or requestedit attributes, as appropriate.
- On-demand rendering of
a view of the object via
the IViewObject, IViewObject2, and IViewObjectEx interfaces.
- Standard OLE Control functionality as provided by the IOleControl interface, and in-place activation using the IOleObject and IOleInPlaceActiveObject interfaces.
- Persistence support for various containers. At a minimum, a control typically provides support so that a container can save the object into a stream using the IPersistStreamInit interface. Many controls will additionally support persistence to a property bag using IPersistPropertyBag because both Visual Basic and Microsoft
In addition, the control needs a graphical user interface that provides a means to edit the control's properties. Typically, a control provides one or more COM objects called property pages, each of which displays a user interface that can modify a logically related subset of the control's properties. A container requests the CLSIDs of the property page COM objects using the control's ISpecifyPropertyPages interface implementation.
A container can also access information about the properties of a control that supports property pages using the IPerPropertyBrowsing interface. For example, the container can obtain a text string describing a property, determine which property page contains the user interface to edit the property, and retrieve a list of strings describing the allowed values for the property.
If the container should provide support for arranging the control's properties by category in the Visual Basic property view, then a control implements the ICategorizeProperties interface to provide the list of categories to Visual Basic and map each property to a category.
Property Page Functionality
Because a control frequently provides one or more property pages, a complete control implementation will also supply one or more property page objects. Each object should do three things. First, it should at least implement the IPropertyPage interface, which provides the main features of a property page object. Second, it should optionally implement the IPropertyPage2 interface to support selection of a specific property. Visual Basic uses this support to open the correct property page and set the input focus directly to the specified control when the user wants to edit a property. Third, it should receive property change notifications from one or more controls using the connection points protocol to the property page's IPropertyNotifySink interface implementation.
BullsEye Control Requirements
We're going to describe the ATL implementation of a feature-rich control called BullsEye. The control is feature-rich in the sense that it implements all the previously described features. The BullsEye control draws a bull's-eye, as shown in Figure 1. You can configure the number of rings in the bull's-eye (from one to nine) and the color of the center ring, as well as the color of the ring adjacent to the center (called the alternate ring color). BullsEye draws additional rings alternately using the center and alternate colors.
The area around the bull's-eye can be transparent or opaque. When transparent, the bull's-eye background shows through. When opaque, the bull's-eye fills the area around the circle using the background color. By default, BullsEye uses the container's ambient background color as the background color. BullsEye also uses the foreground color to draw a line separating each ring.
Figure 1 BullsEye
You can assign score values to each ring. By default, the center ring is worth 512 points and each surrounding ring is worth half the points of its adjacent inner ring. When a user clicks on a ring, the control fires an OnRingHit event and an OnScoreChanged event. The argument to the OnRingHit event method specifies the ring upon which the user clicked. Rings are numbered from 1 to n, where 1 is the centermost ring. The OnScoreChanged event specifies the point value of the clicked ring. For example, clicking on ring 2 with default scores fires an OnScoreChanged event with an argument of 256 points.
In addition, when you click on one of the bull's-eye rings, the control can provide feedback by playing a sound. By default, you hear the sound of an arrow striking the bull's-eye. The Boolean Beep property, when set to TRUE, indicates the control should play its sound on a ring hit.
BullsEye supports all standard control functionality. In addition to windowed activation, BullsEye can activate as a windowless control when its container supports such functionality.
Many containers ask their controls to save their state by using the IPersistStreamInit interface and an IStream medium. When embedding a control in an OLE document, a container asks a control to save its state using the IPersistStorage interface and the IStorage medium. A container (such as Internet Explorer or Visual Basic) that prefers to save the state of a control as textual name/value pairs uses the control's IPersistPropertyBag interface and the IPropertyBag medium. BullsEye supports all three persistence protocols and media: streams, storages, and property bags.
Figure 2 Custom Property Page
Figure 3 Color Selection Property Page
BullsEye also provides two property pages as shown in Figures 2and 3. One property page is custom to the BullsEye control and allows you to set the Enabled, Beep (Sound on ring hit), and BackStyle (Transparent), and Number of rings properties.
The other is the standard color selection property page. The BullsEye control has four color properties: the center ring color, the alternate ring color, the background color (used to fill the area around the bull's-eye), and the foreground color (used to draw the separator line between rings).
Figure 4 Property View
The BullsEye control also categorizes its properties for Visual Basic. Visual Basic has a property view window (see Figure 4) where you can select a view that sorts the properties by standard and control-defined categories. The BullsEye control lists its color properties and the Ring Count property in the standard Appearance category. The control lists its Beep property in the standard Behavior category.
BullsEye also supports per-property browsing. Per-property browsing allows a control to specify a list of strings that a container should display as the available choices for a property's value. Notice in Figure 4 that in the Behavior category the strings "Yes, make noise" and "No, be mute" are the selections available for the Beep property.
Also notice that the Misc category contains an entry called (About) that represents the AboutBox stock method. BullsEye displays this dialog box when the user selects the About entry.
BullsEye supports the four stock properties listed in Figure 5. In addition, BullsEye supports all three stock methods listed in Figure 6 and none of the stock events. BullsEye also supports the custom properties listed in Figure 7.
A control container accesses the properties and methods of a control using the control's IDispatch interface. So a control must provide an implementation of IDispatch when it has properties and methods.
ATL-based controls in general, and the BullsEye control specifically, implement their properties and methods using a dual interface, not a dispatch interface. A dual interface is unnecessary as the vtable portion of the dual interface will typically go unused. A custom C++ control container could access the control's properties and methods using the vtable, but no other container currently does. At the time of this writing, even Visual Basic accesses properties and methods of a control using the control's IDispatch interface. Visual Basic only uses the vtable portion of a dual interface for non-control objects.
The BullsEye control provides access to
its properties and methods on the default IBullsEye dual interface. When you generate a new ATL-based control class, the wizard generates the definition of the default dual interface, but you must populate the definition with the accessor methods for your control's properties and methods. The IDL
in Figure 8 shows the definition of the IBullsEye interface.
BullsEye Custom Events
The BullsEye control doesn't support any of the stock events. However, it has two custom events, as shown in Figure 9.
An event interface contains only methods and should be a dispatch interface for all containers to receive the event callbacks. Some containers, such as Visual Basic, cannot receive event callbacks on custom IUnknown-derived interfaces. An event interface should never be a dual interface.
For the DISPID constants to appear in the MIDL-generated C/C++ header file, the definitions of the constants must appear in the IDL file outside of the library block. You must define the dispinterface itself (listed in Figure 8) inside the library block. You must also define the BullsEye coclass in the library block of the IDL file. At a minimum, you should specify the default dispatch interface (IBullsEye) through which a container can access BullsEye's properties and methods, and the default source interface (_IBullsEyeEvents) through which the BullsEye control fires events to its container. Additionally, you should define all the custom property page classes implemented by your control in the library block of the IDL file. BullsEye only has one custom property page class, BullsEyePropPage.
Creating the Initial Control
Some people prefer to write all the code for a control by hand. They don't care for the wizard-generated code because they don't understand what it does and doesn't do. Even if you generate the initial code base using the wizard, you will change it greatly before the control is complete anyway, so you might as well save some time and effort initially by using the wizard. But you must understand what you're getting.
We took the requirements for the BullsEye control, used the ATL Object Wizard, and requested a Full Control since that's the option that most resembles our requirements. Here were our responses to the wizard dialogs.
Figure 10 Project Names
In Figure 10, we defined the name of our implementation class (CBullsEye), its source file names, the primary interface name (IBullsEye), and various COM object registration information. Figure 11 shows our selections for various COM object options for
the control. Controls need thread affinity because they are UI components and use window handles that are associated with a thread. Therefore, the wizard correctly allows you
to request only the single or apartment threading models for a control.
Figure 11 COM Options
Containers will access a control's properties and methods by using the control's IDispatch interface. The easiest way to get an IDispatch implementation in your control is to specify that the primary interface be a dual interface. If
you specify the Custom interface option, you'd need to implement IDispatch separately on the control. Controls can be aggregated. We'll request that BullsEye supports aggregation, though it does increase the size of each instance by 8 bytes.
A COM object that implements a dual interface should also support ISupportErrorInfo so it can return rich error information using the COM error object mechanism. The BullsEye control fires events to containers that expect to receive control events via the connection points protocol, so we asked for connection point support as well.
A control should never use the Free Threaded Marshaler, so be sure to leave that selection unchecked.
Figure 12 View Options
Figure 12 shows the Miscellaneous page, which allows you to select various control options not available elsewhere. The only options that apply to the BullsEye control are the ones shown here. Select the Opaque option when your control is completely opaque and none of the container shows through within the control's boundaries. This helps the container draw the control more quickly. An opaque control can also specify that the background is a solid color, not a patterned brush. We'll discuss how to implement the BullsEye rendering code so that it supports transparent areas around the bull's-eye. But, let's start with an opaque control. The Opaque and Solid Background options simply specify the flags used in the DECLARE_VIEW_STATUS macro.
When selected, the Normalize DC option causes your control to override the OnDraw method for its rendering. When not selected, the control overrides the OnDrawAdvanced method. By default, OnDrawAdvanced saves the state of the device context, switches to MM_TEXT mapping mode, calls OnDraw, and then restores the saved device context. Therefore, when you ask for a Normalized DC and don't override OnDrawAdvanced, you introduce a little more overhead. BullsEye uses this support, though.
The Insertable option allows the
control to be embedded by any application that supports embedded objects through the Insert Object system dialog. Microsoft Excel and Word are two such applications. Among other things, this causes your control to support the IPersistStorage and IDataObject interfaces.
Finally, you can have the wizard generate support for any stock properties you want the control to support. BullsEye requires four stock properties. You'll have to slightly enhance the IDL for the stock properties, but ATL provides the implementation of the property accessor methods. There is no wizard support for stock methods, so you'll have to implement them, as well as BullsEye's custom properties, by hand.
The Initial CBullsEye Class Declaration
Figure 13 shows the initial wizard-generated class declaration for the CBullsEye control class. We'll have to make a number of changes to the class before it meets all the requirements described earlier. We've reformatted the source code slightly from the original wizard-generated source code to group related functionality together and
to add a few comments. No properties are supported by
the control except the stock properties we selected via the wizard dialogs. There is quite of bit of implementation
code to write to make the control draw and behave as a bull's-eye.
We'll need to alter the initial wizard-generated IDL
description for the IBullsEye interface to match the one shown in Figure 8. Initially, the wizard generates the interface containing any stock properties you've specified. We'll need to add all the custom properties for the control as well as any
stock and custom methods supported by the control.
The initial _IBullsEyeEvents dispatch interface is empty, so we'll have to add the BullsEye custom event methods (shown in Figure 9) to the dispinterface. When a control supports any of the stock events, you'd add them to the event interface as well.
Updating Stock Properties and Methods in the IDL
Your IDL file describing the control's default dispatch interface must contain an entry for each stock property accessor method and all stock methods you support. The ATL Object Wizard will generate these method definitions for those stock properties added using the ATL Object Wizard dialogs.
There are a couple of changes you must make manually to the method definitions. First, all stock properties should have the bindable and requestedit attributes. This is because the stock property put methods fire change notifications to a container before and after changing a property. Therefore, you need to change each method like this:
// Original wizard-generated version
HRESULT BackColor([in]OLE_COLOR clr);
HRESULT BackColor([out,retval]OLE_COLOR* pclr);
// Corrected version
[propput, bindable, requestedit, id(DISPID_BACKCOLOR)]
HRESULT BackColor([in]OLE_COLOR clr);
[propget, bindable, requestedit, id(DISPID_BACKCOLOR)]
HRESULT BackColor([out,retval]OLE_COLOR* pclr);
|This change has no effect on the way the control actually operates, but now the control's type library more accurately describes the actual behavior of the control.
You'll need to add any stock methods explicitly to the
default dispatch interface definition in your IDL file. There are only three stock methods presently defined (AboutBox, DoClick, and Refresh), and BullsEye supports them all. I've added the following lines to the IBullsEye interface definition.
[id(DISPID_ABOUTBOX)] HRESULT AboutBox();
[id(DISPID_DOCLICK)] HRESULT DoClick();
[id(DISPID_REFRESH)] HRESULT Refresh();
Implementing Properties and Methods with CStockPropImpl
The CStockPropImpl class contains an implementation of the property accessor (get and put) methods for all stock properties. These methods notify and synchronize with the control's container when any stock property changes.
The CStockPropImpl class contains an implementation of every stock property you can choose from the Stock Properties tab in the ATL Object Wizard. A control derives from CStockPropImpl when it wants an implementation of any of the stock properties. The declaration of the template class looks like this:
template < class T, class InterfaceName,
const IID* piid, const GUID* plibid>
class ATL_NO_VTABLE CStockPropImpl :
public IDispatchImpl< InterfaceName, piid, plibid >
|The class T parameter is the name of your control class. The InterfaceName parameter is the name of the dual interface defining the stock property propget and propput methods. The CStockPropImpl class implements these accessor methods. The piid parameter is a pointer to the IID for the InterfaceName interface. The plibid parameter is a pointer to the GUID of the type library that contains a description of the InterfaceName interface. The CBullsEye class implements its stock properties using CStockPropImpl like this:
class ATL_NO_VTABLE CBullsEye :
public CStockPropImpl<CBullsEye, IBullsEye,
Most controls don't need support for all the possible stock properties. However, the CStockPropImpl base class contains supporting code for all stock properties. This code needs a data member for each property. ATL expects your deriving class, the control class, to provide the data members only for the stock properties that your control supports (except for AUTOSIZE and HWND). You must name these data members with the exact same variable names used by the CStockPropImpl class. Figure 14 lists the appropriate name for each supported stock property.
The CStockPropImpl class contains references to all of these member variables because it contains property accessor methods for all of these properties. So that a control does not need to allocate space for all properties when it only needs to support a few properties, the CComControlBase class defines a union of all these member variables. CComControlBase itself is the base class for CComControl, from which CBullsEye derives.
// m_nFreezeEvents is the only one actually used
// These are here to make stock properties work
|The net result of this space optimization is that, when you add a member variable to your control class to hold a stock property and you misspell the member variable name, you will receive no compilation errors. The code in CStockPropImpl simply references the field of the previous anonymous union contained in your control's base class. Typically, this is not the behavior you want.
The ATL Object Wizard generates the proper member variables in your control's class when you initially add a stock property. For example, here are the member variables generated for the stock properties in the CBullsEye class:
CStockPropImpl implements explicit put and get methods for the stock properties that are interface pointers, including FONT, MOUSEICON, and PICTURE. For each additional stock property, CStockPropImpl invokes one of three macros that expand to a standard put and get method for the property. These macros are IMPLEMENT_STOCKPROP, IMPLEMENT_BOOL_STOCKPROP, and IMPLEMENT_BSTR_STOCKPROP.
The IMPLEMENT_STOCKPROP(type, fname, pname, dispid) macro expects to be used in a template class containing a template parameter named T. It expects the class T to contain a declaration of a member variable for a property of the specified type. The macro references the member variable by prefixing m_ to pname. For example, when pname is bEnabled, the macro references m_bEnabled.
The Macro defines a put method and a get method for the property. It creates the put and get method names by prefixing put_ and get_ to fname, respectively. For example, when fname is Enabled, the method names are put_Enabled and get_Enabled. The put method notifies the container when the property changes. It also associates the DISPID, dispid, with the property.
The IMPLEMENT_BOOL_STOCKPROP(fname, pname, dispid) macro implements a stock Boolean property's accessor methods. It has the same attributes as described for the IMPLEMENT_STOCKPROP macro except that the get method tests the value of the data member containing the property and returns VARIANT_TRUE or VARIANT_FALSE rather than
returning the value. The IMPLEMENT_BSTR_STOCKPROP(fname, pname, dispid) macro implements a stock text property's accessor methods using a BSTR.
Note that these macros do not work as presently described in the documentation. They do not allocate the member variable and cannot be used in your control class. Basically, they are only used within the CStockPropImpl template class.
Let's look at the implementation of the IMPLEMENT_STOCKPROP macro (shown in Figure 15). There are a couple of other issues illustrated by the ATL 3.0 code that are worth noting and that apply to all stock properties. First, notice that the put method fires OnRequestEdit and OnChanged event notifications to the control's container before and after changing the value of a stock property. This behavior is why we changed the IDL for the stock properties to add the bindable and requestedit attributes.
Second, the put method fires the OnRequestEdit and OnChanged events without regard to a control's freeze event count. When a control's freeze event count (maintained in CComControlBase in the m_nFreezeEvents member variable) is nonzero, a control should hold off firing events or discard them completely. The failure of property put methods to obey this rule causes some containers to break. For example, the Test Container application shipped with Visual C++® 6.0 crashes when a control fires change notifications for custom properties in its FinalConstruct method. A control should be able to call FreezeEvents(TRUE) in FinalConstruct to disable change notifications, initialize its properties using the put methods, and then call FreezeEvents(FALSE) to enable change notifications if they were previously enabled.
Third, changing a stock property sets the m_bRequiresSave member variable of your class to TRUE. A control inherits this member variable from the CComControlBase class, so you don't explicitly provide it. Theoretically, though, you might want to support stock properties in objects that aren't controls such as server-side components. Such an object would have to provide its own definition of this member variable.
Occasionally, you'll decide to support additional stock properties after creating the initial source code. The wizard doesn't have any support for adding features to your class after the initial code generation, so you'll have to make the previously described changes to your code manually.
Finally, you'll often want to do some work over and above what the stock property put functions perform. For example, the CBullsEye class needs to know whenever the background color changes so it can delete the old background brush and schedule the rendering logic to create a new background brush. To do this, you have to override the put_BackColor method provided by the CStockPropImpl class. Basically, this means you often end up rewriting most of the stock property put methods. You can reuse the get methods, but they are for the most part trivial.
It would be nice if future versions of CStockPropImpl put methods called a pT->OnStockPropChanged member function (such as OnBackColorChanged) to notify their deriving class that a particular stock property has changed value. CStockPropImpl could provide empty inline functions for all the stock properties and, therefore, not incur any overhead for the function when it wasn't overridden.
Adding Custom Properties and Methods
In addition to any stock properties, your control's default dispatch interface must contain an entry for the property get and put methods for each custom control property, as well as all the stock and custom methods you support. The ATL Object Wizard doesn't currently support stock methods so you'll add them to your class as if they were custom methodswhich they are, except you don't get to choose the function signatures.
You can use the Visual C++ IDE to add the properties and methods to an interface, or you can make the source code changes manually. To use the IDE, right-click on the interface name in the Class View and select Add Method or Add Property. After you respond to the resulting dialogs, the IDE changes three parts of your project. First, it updates the IDL for the specified interface and adds the appropriate property or method definition. Second, it updates your control class' declaration (.h file). Third, it adds a skeletal function body for each property accessor function or method. We prefer our methods to reside in the implementation (.cpp) file, so we frequently convert the skeletal function body into a function prototype and place the function body in the class' implementation file.
Implementing Custom Properties and Stock and Custom Methods
You'll need to add a function prototype to your control class for each method added to the IDL in the previous step. When you use the IDE, it adds a skeletal function body for each method. For the CenterColor custom property and stock method, we added the following function prototypes to the CBullsEye class:
STDMETHODIMP get_CenterColor(/*[out, retval]*/
STDMETHODIMP put_CenterColor(/*[in]*/ OLE_COLOR
There are a couple of changes you must make manually to the method definitions. First, all stock properties should have the bindable and requestedit attributes. This is because the stock property put methods fire change notifications to a container before and after changing a property.
The function implementations are all pretty straightforward. The get_CenterColor method validates its argument and returns the value of the CenterColor property.
if (NULL == pVal) return E_POINTER;
*pVal = m_clrCenterColor;
|The put_CenterColor method, like all property change functions, is a bit more complicated.
if (m_clrCenterColor == newVal) return S_OK;
m_clrCenterColor = newVal; // Save new color
m_centerBrush = NULL;
m_bRequiresSave = TRUE; // Set dirty flag
if (!m_nFreezeEvents) // Notify container
FireViewChange(); // Request redraw
SendOnDataChange(NULL); // Notify advise sinks
First, the method checks to see if the new value is the same as the current value of the CenterColor property. If so, the value isn't changing, so we exit quickly. Then, unlike the current stock property code, it properly checks to see if the container presently doesn't want to receive eventsthat is, if the freeze events count is nonzero. When the container has not frozen events, the put_CenterColor fires the OnRequestEdit event to ask the container for permission to change the CenterColor property. When the container refuses the change, put_CenterColor returns S_FALSE. When the container grants permission, put_
CenterColor updates the member variable in the control that contains the color. It also changes some values that cause the control's rendering code to use the new color the next time the control redraws.
After the method changes the property, it sets the control's dirty flag (m_bRequiresSave) to remember that the state of the control now needs to be saved. The various persistence implementations check this flag when executing their IsDirty method. Next, the method fires the OnChanged event to notify the container of the property change, assuming events are not frozen, of course.
The CenterColor property affects the visual rendering of the control. When a control changes such properties, the control should notify its container that the control's appearance has changed by calling the FireViewChange function. In response, the container will eventually ask the control to redraw itself. After that, the method notifies all advise sinks (which typically means the container) that the state (data) of the control has changed by calling SendOnDataChange. Note that the state of a control changes independently of the control's view. Some control property changes, like changes to CBullsEye's Beep property, have no affect on the appearance of the control, so the put_Beep method doesn't call FireViewChange.
The stock AboutBox method simply displays the Help About dialog.
Adding Stock and Custom Events to the IDL
Your IDL file describing the control's default source interface must contain an entry for each stock and custom event method you support. As described previously, for maximum compatibility with all control containers you should implement the default source interface as a dispatch interface. There is no current support in the IDE for adding event methods to a dispinterface.
The BullsEye control needs to support the two custom events described in Figure 9. You'll also want to make sure that the IDL correctly describes the BullsEye class itself. The BullsEye coclass in the library block of the IDL file definition should define IBullsEye as the default interface for the control, and the _IBullsEyeEvents dispatch interface as the default source interface.
That's enough for this month. In a future article we'll add lots of additional functionality to the BullsEye control. We'll cover support for firing events and property change notifications from the control (which surprisingly often require implementing the IProvideClassInfo2 interface). We'll show you how the control should draw itself and perform mouse hit-testing when it contains transparent areas and isn't rectangular. We'll also add support to BullsEye for persistence, component categories, quick activation, categorizing its properties in the Visual Basic Property View window, and per-property browsing. The source code for BullsEye contains the complete implementation of all these features, plus more. So download away if you can't wait. Our next article (March 1999) will point out numerous subtle aspects of the code and a few flat-out ATL bugs. See you then!
For more information, see Dynamic Resizing of ActiveX controls hosted by ATL, Discussions in ActiveX Template Library, INFO: Active Template Library FAQ, and How to Write and Use ActiveX Controls.
From the February 1999 issue of Microsoft Systems Journal.
Get it at your local newsstand, or better yet, subscribe.