Once your control is up and running, you can extend its functionality.
In many ways, your control is much like a regular Windows program. Even if your control doesn't have a window (for example, it uses the windowless features of the ActiveX specification), you’re using a window process so you have to paint the client area yourself using regular windows-drawing APIs and handles to device contexts (DC).
To declare your control, use either DEFINE_CONTROLOBJECT or DEFINE_WINDOWLESSCONTROLOBJECT in your control's header file. These structures are self-documenting, but two parameters are worth a little extra discussion.
The first is the dwActivationPolicy parameter. The framework allows your control to take advantage of the delayed-activation features provided by IPointerInactive. If your control uses any of these, you should insert an activation policy into this field. By default, it is 0. Subclassed windows controls should not change this. Other control authors may wish to take advantage of this interface, and should consult the ActiveX specification for more information.
The second parameter is a Boolean value passed in to the DEFINE_WINDOWLESSCONTROL macro, which indicates whether your control has any transparent regions. If this parameter is True, the host has to ensure that your control paints correctly in the z-order of things. If False, your control is required to paint its entire client area, but the host has to do a little less work.
Every control in the framework must implement a core set of methods, based on creation semantics and methods that aren’t provided by COleControl. You may also want to override and provide an implementation for several routines. The following table notes many of these methods and routines.
Method/routine | Discussion |
static Create | The control wizard generates this routine for you. Every control must create its control object in this routine and then return a pointer to its private unknown (for aggregation support). |
Constructor and Destructor | These are generated for you. You should minimize initializations in the constructor to prevent load time from degrading. |
RegisterClassData | Required. Even if your control is windowless for the most part, a user may place your control in a host that doesn't support all of the ActiveX features, and the framework will be forced to create an HWND for your control. In this case, you will need a window class. This routine is called only the first time a control of the given type is loaded in a process in which it must have an HWND. Controls should register their window class (using RegisterClass and the WNDCLASS structure) here. In addition, subclassed windows controls should get a pointer to the parent control's WindowProc and set that up in the g_ObjectInfo table using the SUBCLASSWNDPROCOFCONTROL macro. Controls that are invisible at run time should return FALSE in this routine, because it should never get called. |
BeforeCreateWindow and AfterCreateWindow | Optional. BeforeCreateWindow is called right before the call to CreateWindow if your control is windowed, but after persistent state has been loaded. Controls should use this opportunity to set the window title for their control in m_szWindowTitle, and can also set up bits in pdwWindowStyle and pdwWindowStyleEx parameters that are passed in. In addition, controls can set their initial caption here in the parameter passed in for this purpose. This does, however, have a limit as defined in CreateInPlaceWindow in Ctlmisc.cpp. Control writers should use this opportunity to set up these defaults as much as possible, as opposed to in the message handler for WM_CREATE, because it typically results in much better performance. |
InternalQueryInterface | Your control implements this to support QueryInterface for the control's primary Automation interface, such as IMyControl. You can also use this method to support additional interfaces in your control. For example, if you want to support IPerPropertyBrowsing, set up your control's CMyControl class to inherit from IPerPropertyBrowsing, and support the QueryInterface for IID_IPerPropertyBrowsing in InternalQueryInterface. If the control fails the QueryInterface, then the control should delegate back to COleControl::InternalQueryInterface to see if it accepts the IID. |
LoadTextState, LoadBinaryState, SaveTextState, and SaveBinaryState | Required. All controls must implement these persistence routines. See “Persistence” later in this appendix for a discussion of these interfaces. |
OnDraw | This routine is called when your control is expected to draw itself. At design time, this call originates from a container calling IViewObject2::Draw. At run time, the controls framework intercepts the WM_PAINT message and translates it into a call to your OnDraw routine. See “Painting a Control” later in this section for more information. |
WindowProc | Messages that are not handled in the framework code (such as SimpleFrame messages and WM_PAINT) are sent to your control here. Your control should deal with these messages here. See “Handling Messages in a Control” later in this section for a discussion of this routine. |
OnSpecialKey | Messages for various keyboard events, such as moving cursor keys, function keys, and other nonstandard keys, do not go to the WindowProc. Instead, they are sent to the OnSpecialKey routine. If you want your control to handle special keys and accelerators, you should override and implement this routine. The control should return TRUE if it handles a key, or FALSE if it ignores the key. |
DoCustomVerb | If your control implements custom verbs in addition to the default property page verb (provided your control has a property page), you should implement this routine and take appropriate action depending on what verb was sent in. Return OLEOBJ_S_INVALIDVERB if you don't recognize the verb given. |
OnSetExtent | This is called every time your control is resized. The m_Size SIZEL structure is your control's current size in pixels. Controls should look in here for size information and override OnSetExtent if they want to control how their control is sized. |
OnQuickActivate | This routine is called if your control is in a host that supports quick activation and has used IQuickActivate::QuickActivate to activate your control. Your control is given a pointer to a QACONTAINER structure, and should examine the data in it. A nontrivial number of ambients are passed to the control in this way and will save later calls to GetAmbientProperty. See the documentation for IQuickActivate in the ActiveX specification for more information. |
In addition, the following methods and routines can be called by an ActiveX control, and often prove to be extremely useful.
Method/routine | Discussion |
DoSuperClassPaint | Subclassed window controls can call this from their OnDraw routines to paint themselves. For most window controls, this routine paints them correctly in design time and run time. Some Windows controls, however, require more attention. |
RecreateControlWindow | Used for subclassed controls; re-creates the control's hWnd. This is useful if the control is changing a style bit that can't be changed with a SetWindowLong(GWL_STYLE ...) call. |
DesignMode | Returns a Boolean value indicating its best guess as to whether the environment is in design mode. If it can't figure it out, it returns False. |
GetAmbientProperty | Gets an ambient property from the container. Not all containers return these (they might not support them), so be careful to check the return code. |
GetAmbientFont | Gets the current ambient font. A container may not implement this. Controls are encouraged to look for the ambient font in the QACONTAINER structure passed in to OnQuickActivate. Avoid calling GetAmbientFont to save time. Don't forget to release the font once you're finished with it. |
ModalDialog | Controls must call ModalDialog before they show a modal dialog box. You can see this when the control is about to show its About Box dialog box. |
InvalidateControl | Forces a control to be repainted if you pass in NULL for the rectangle, or invalidates the given area if it's not NULL. This is similar to the InvalidateRect API, but also operates in design mode. |
SetControlSize | Controls that change their size out of OnSetExtent should use this routine to set their size. The control passes in a SIZEL structure in pixels and expects a call to OnSetExtent. Be careful of some recursive situations. |
PropertyChanged | Whenever the value of a property changes, this routine should be called to notify a host. This causes the host to update any property browsers (such as those in Microsoft Visual Basic 5.0). |
RequestPropertyEdit | Whenever a control wants to change a property that is marked as requestedit in the .odl file, the control must call this first and check the return code (TRUE or FALSE). |
GetResourceHandle | Controls should call this whenever they're loading a resource that could be localized. This routine gets the handle to the appropriate DLL and deals with satellite DLLs or the lack thereof. |
FireEvent | A control passes this routine an EVENTINFO structure, and an event as described in the EVENTINFO is fired. The control also passes parameters to this routine because it is a varargs method. |
ControlFromUnknown | Property page code often finds it useful to get the COleControl * pointer from the IUnknown for a control object. This routine does just that. |
Exception | Your control, or any Automation object, can use this to send the user an error message. See “Throwing an Exception” later in this section for more information about using this routine. |
Windowless controls that want to support windowless in-place activation have routines they must call instead of the Win32® APIs to get the correct functionality. The following is a list of the Win32 functions and their replacements.
Win32 function | Replacement function |
GetFocus | OcxGetFocus. Returns a Boolean value indicating whether the control has keyboard focus. |
SetFocus | OcxSetFocus. Takes a Boolean parameter indicating whether you want to take the focus or give it up. |
GetCapture | OcxGetCapture. Indicates whether or not you have the keyboard capture. |
SetCapture | OcxSetCapture. Used to ask for or release the keyboard capture. |
GetDC/ReleaseDC | OcxGetDC and OcxReleaseDC. Provides this functionality for windowless controls. |
InvalidateRect | OcxInvalidateRect. Lets you force a repaint of your control. |
These routines all work correctly if your control has an HWND. If you don’t intend for your control to work without an HWND, then you can safely use the regular Win32 APIs.
The OnDraw routine is called whenever the control needs to paint. Sometimes the origin of the call is IViewObject2::Draw (as in design mode), and other times it comes from being sent a WM_PAINT message (as handled in ControlWindowProc).
Your control is given a device context (DC), a rectangle to describe where to paint, a rectangle for describing a metafile, and an Information Context (IC, passed in as an HDC) that describes the device. If the device is a metafile, the control must do a little different work. However, if the device is a raster display, the control typically paints to the screen.
Your control must be careful not to make any assumptions about the device context, except that it will be in MM_TEXT mapping mode. Often, there are no default pens, brushes, fonts, or colors selected into the device context. Your control has to do this work itself. This typically means your control will look slightly different in design and run modes.
If the fOptimize parameter passed in is TRUE, then the control writer can take advantage of the optimizations described in the ActiveX specification for not fully cleaning up the device context on exit. Writers are still responsible for deleting GDI objects they create and load, but they can leave things selected in the device context on exit if this parameter is TRUE. See the ActiveX specification for more information.
Finally, the dvAspect parameter is passed in to the OnDraw routine. This is used for multipass drawing, as described in the ActiveX specification (IViewObjectEx). Authors not wishing to support multipass drawing should ignore this parameter; the default setting does not support it.
Your control has a method called WindowProc, which is called whenever a message is sent to your control. Your control should respond to messages here. If your control has an HWND, try to reduce the amount of work that is done in the WM_CREATE message handler; instead, try to place it in BeforeCreateWindow.
For certain types of messages, such as keyboard messages for arrow keys and other special keys, your WindowProc routine is not called. OnSpecialKey is called instead. The code in OnSpecialKey should look for WM_KEYDOWN, WM_KEYUP, WM_CHAR, and other messages, and handle them appropriately.
A certain class of messages typically involves notifying a window about events that are usually sent to a window's parent. These include WM_COMMAND, WM_NOTIFY, and WM_CTLCOLOR. These messages are reflected by the host to your control in the form of OCM_COMMAND, OCM_NOTIFY, and OCM_CTLCOLOR. Your controls should look for these messages instead of WM_ messages. See Olectl.h for other OCM_ messages of interest to the control. This discussion does not apply to windowless controls.
One of the more important parts of a control is often its set of properties. When you create a control with the wizard, you have no properties by default. Adding them is relatively simple.
First, modify the primary dispatch interface for your control in the .odl file. For example, you may have a control called SuperScroll, and you want to add a LargeChange method to the control. You would add the following to the ISuperScroll interface description in the .odl:
[id(DISPID_LARGECHANGE), propget, helpstring("The largechange property")]
HRESULT LargeChange([out, retval] long *plLargeChange);
[id(DISPID_LARGECHANGE), propput]
HRESULT LargeChange([in] long lLargeChange);
Next, you define DISPID_LARGECHANGE in dispids.h. You then regenerate the type library (.tlb) file by typing this: make SuperScroll.tlb
This also regenerates SuperScrollInterfaces.h. You can then cut and paste the following two lines from SuperScrollInterfaces.h:
STDMETHOD(get_LargeChange)(THIS_ long FAR* plLargeChange) PURE;
STDMETHOD(put_LargeChange)(THIS_ long lLargeChange) PURE;
Finally, take these two lines and add them to your class description for CSuperScroll, and make sure that you remove the PURE declarators at the end. For example:
STDMETHOD(get_LargeChange)(long FAR* plLargeChange);
STDMETHOD(put_LargeChange)(long lLargeChange);
You can now implement these methods in your control file to implement your property.
Note A few standard dispids are defined for you in olectl.h. Whenever you want to declare a property, look in this header first to see if there is a standard dispid for it.
Adding a method is much like adding a property to your control.
First, you define a dispid for the method. Once you've done that, it's as simple as adding the method to the primary interface for your control. Controls generated with the control wizard already have an About method defined for them.
As an example, define a method called MooCow with three parameters, the last of which is optional. Here is one such method:
[id(DISPID_MOOCOW), helpstring("Makes your Cow moo")]
HRESULT MooCow([in] long lSeconds, [in] boolean fLowPitch,
[in, optional] VARIANT vPitch);
Again, as with properties, you regenerate the type library and then paste the declaration into your control header (without the PURE declarator) and implement it.
In many situations, you may want your control to fire an event. For example, when a control gets a WM_LBUTTONDOWN message, it often makes sense to fire a MouseDown event. This turns out to be easy.
First, you must define the event in an EVENTINFO structure. There are many ways to do this, including declaring a new global variable for each event type or using an array; an array is neater and is discussed here. Say you want to have KeyDown, KeyUp, and KeyPushed events.
Here's how you might declare them:
typedef enum {
MyCtlEvent_KeyDown = 0,
MyCtlEvent_KeyUp = 1,
MyCtlEvent_KeyPushed = 2
} MYCTLEVENTS;
VARTYPE rgI2 [] = { VT_I2 };
EVENTINFO m_rgMyCtlEvents [] = {
{ DISPID_KEYDOWN, 1, rgI2 },
{ DISPID_KEYUP, 1, rgI2 },
{ DISPID_KEYPUSHED, 1, rgI2 }
};
The EVENTINFO structure has three members: the dispid of the event, the count of arguments in the event, and a pointer to an array of VARTYPE that describes the types of the parameters to the event. Remember, there are dispids defined for you in Olectl.h; whenever you're adding an event, check there first to see if adispid exists for it.
To fire these events from code, just call the following:
FireEvent(&(m_rgMyCtlEvents[MyCtlEvent_KeyDown]), sKeyValue);
For many controls, you may find it useful to declare properties of types provided by OLE, such as Font, Picture, and Color. Many hosts detect properties of these types and put up convenient browsers for the user to select values for these types.
To declare a property of one of these types, you must first make sure its .odl file includes the following at the top:
importlib(STDTYPE_TLB);
Then, to declare a property of type Font, Picture, or Color, your code would be similar to the following, depending on the type:
[id(DISPID_FONT), propget]
HRESULT Font([out, retval] IFontDisp **ppFont);
[id(DISPID_FONT), propputref]
HRESULT Font([in] IFontDisp *pFont);
[id(DISPID_MOUSEICON), propget]
HRESULT MouseIcon([out, retval] IPictureDisp **ppMouseIcon);
[id(DISPID_MOUSEICON), propputref]
HRESULT MouseIcon([in] IPictureDisp *pMouseIcon);
[id(DISPID_FORECOLOR), propget]
HRESULT ForeColor([out, retval] OLE_COLOR *pocForeColor);
[id(DISPID_FORECOLOR), propput]
HRESULT ForeColor([in] OLE_COLOR ocForeColor);
For the get_ and put_ methods for these types, the control gets a property as declared above. For fonts and pictures, the control probably will use QueryInterface for IFont and IPicture, respectively.
For your control to use a font object, the control typically calls the get_hFont method and passes the resulting HFONT to the control's device context. It handles pictures in much the same way. For fuller descriptions of using these fonts in your application, see the Microsoft Developer Network (MSDN).
To use a color, the control calls OleTranslateColor to convert it to a real COLORREF. An OLE_COLOR is basically a COLORREF with some support for "generic" colors, such as COLOR_WINDOW and COLOR_WINDOWTEXT. To convert one of these into an OLE_COLOR, just join it in a logical OR (|)with 0x80000000. For example, to initialize the control's background to COLOR_WINDOW, set the backcolor property to COLOR_WINDOW | 0x80000000
. Then, to paint the backdrop, just call OleTranslateColor and use the resulting COLORREF.
Note The framework uses dual/vtable bound Automation interfaces, and uses Automation functionality to support IDispatch methods on the Automation objects.
Every once in a while, during an operation, your control may need to communicate problems to the user. It does so by throwing an exception. In any of the control's Automation methods or property operators, the control can call the Exception method when exiting, and the method sets up all the appropriate information to trigger the error.
For example:
CMyControl::put_Appearance(long lAppearance)
{
if (lAppearance == 10)
return Exception(MYCTL_E_INVALIDPROPERTYVALUE, IDS_ERR_INVALIDPROPERTYVALUE, 0);
m_lAppearance = lAppearance;
return S_OK;
}
The arguments to the Exception routine are as follows: the first is the SCODE of the error the control wants to trigger. For errors unique to the control, you should define them like so:
#define MYCTL_E_INVALIDPROPERTYVALUE MAKE_SCODE(SEVERITY_ERROR, FACILITY_CONTROL, 34500)
The second argument is the resource identifier of the string that the control should display. The Exception code correctly gets this information from the control's localized satellite DLL.
The last argument is the helpcontextid that is passed to the help file that is defined in your control's structure.