Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

ATL Control Classes

The Active Template Library (ATL) offers advantages over MFC when building ActiveX controls when size or performance is a consideration. A control built with ATL is always smaller than an equivalent control built using MFC. In addition, you can build an ATL control that has no external dependencies—simplifying distribution, especially to network clients.

The classes used primarily for ActiveX controls follow:

CWindow Similar to the MFC CWnd class, but much lighter in weight. This class provides easy access to a number of commonly used functions.
CWindowImpl Derived from CWindow and used by the control class to subclass or superclass an existing window class. It implements the ATL message map.
CDialogImpl Derived from CWindow and used to implement dialog boxes.
CContainedWindow Derived from CWindow and used to model a window contained in another object. If you superclass or subclass an existing window, an instance of CContainedWindow will represent the original window.

In addition to these classes, ATL provides support for adding properties, events, and connection points to your ActiveX control. The following sections discuss these features.

Implementing Stock Properties Using ATL

The easiest way to add a stock property to a control built with ATL is with the ATL Object Wizard. The Stock Properties tab enables you to select stock properties to be added to your control by moving the property name between two lists (see Figure 31.1).

31fig01.gif

Figure 31.1 The ATL Object Wizard's Stock Properties tab.

The ATL Object Wizard creates a property map in your ATL control's class header that manages the properties exposed by the control. The property map begins with the BEGIN_PROP_MAP macro and ends with the END_PROP_MAP macro. Inside the property map are a series of macros, each specifying a property supported by the control (see Listing 31.1).

Example 31.1. An ATL Property Map

BEGIN_PROP_MAP(CShapedButton)
    PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
    PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
    PROP_ENTRY("Caption", DISPID_CAPTION, CLSID_NULL)
END_PROP_MAP()

Stock properties are implemented using the PROP_ENTRY macro. PROP_ENTRY has three parameters:

Each stock property has a specific DISPID that refers to the property. DISPID_CAPTION is the dispatch ID for the control's label, for example. The ATL Object Wizard knows all about these names. If you're adding a stock property by hand, you have two choices:

In addition to adding an entry to the property map, the ATL Object Wizard adds a member variable to your class declaration for every property exposed by your control. The wizard doesn't add any code that explicitly maps the variable name to the property, however; it's not needed.

The mapping between variable names and properties is done in the ATL class library. Every stock property is mapped to a particular member of an anonymous union declared in the ATL CComControlBase class. ATL Object Wizard uses a name from this union when adding member variables for properties to your control. If you're adding a property by hand, you again have two choices:

Whichever method you choose for selecting the variable name, make sure you name the variable correctly. If you declare a variable with a different name, your project will compile, but your property value won't be updated properly.

Implementing Custom Properties Using ATL

Implementing custom properties is similar to implementing stock properties. One obvious difference between stock and custom properties is that you must name the custom properties yourself. You add custom properties by right-clicking the IDL icon in the ClassView window and choosing the Add Properties item from the pop-up menu. A dialog box appears, as shown in Figure 31.2.

31fig02.gif

Figure 31.2 Adding a property to an ATL object.

You use the Add Property to Interface dialog box to add the named property, the IDL statements, and the glue code necessary in your class so that the property can be exposed to the outside world.

You must provide the following values:

Property Type The Automation-compatible type that contains the property.
Property Name The name of the property as it will be exposed to the outside world. This is the name Visual Basic or Visual InterDev displays in property pages for your control, for example.
Parameters Any additional parameters you want to apply to the property.
Get Function If you enable this check box, the option generates a function to retrieve the property value from the control. If you disable this option, the property cannot be read.
Put Function If you enable this check box, the option generates a function to set the control's property. If you disable this option, the property is read-only.
PropPut/PropPutRef If you select the PropPutRef radio button, the property is set by reference, which is more efficient for large objects. If you select PropPut, the property is set with a parameter that is passed by value.

You can click the Attributes button to set IDL attributes, such as hidden or call_as, for the property.

Consider a project named TestBtn that exposes a BevelSize property, for example. To enable this property, you fill in the Add Property to Interface dialog box with the values shown in Table 31.1.

Table 31.1. Sample Values for the Add Property to Interface Dialog Box

Control Value
Property Type long
Property Name BevelSize
Parameters (none)
Get Function Checked
Put Function Checked
PropPut Selected

Given these values, Visual C++ creates the following IDL for the new property:

interface ITestButton : IDispatch
{
    [propget, id(1), helpstring("property BevelSize")]
    HRESULT BevelSize([out, retval] long *pVal);
    [propput, id(1), helpstring("property BevelSize")]
    HRESULT BevelSize([in] long newVal);
};

Visual C++ also will create class member functions to implement the necessary interface methods declared by the IDL fragment. What Visual C++ will not do is declare a member variable for you. You must add a member variable (if needed) and fill in the skeleton member functions provided by Visual C++, as Listing 31.2 shows.

Example 31.2. Using Member Functions to Provide Access to Custom Properties

STDMETHODIMP CTestButton::get_BevelSize(long *pVal)
{
    *pVal = m_nBevelSize;
    return S_OK;
}

STDMETHODIMP CTestButton::put_BevelSize(long newVal)
{
    m_nBevelSize = newVal;
    return S_OK;
}

Visual C++ will not automatically add the custom property to the property map, so the property will not be persisted automatically. If you want the property to be persistent, you must add an entry to the property map using the PROP_DATA_ENTRY macro, as Listing 31.3 shows.

Example 31.3. Adding a Custom Property to the Property Map

BEGIN_PROP_MAP(CSlaskPop)
    PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
    PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
    PROP_DATA_ENTRY("BevelSize", m_nBevelSize, VT_I4)
    PROP_ENTRY("Caption", DISPID_CAPTION, CLSID_NULL)
END_PROP_MAP()

The PROP_DATA_ENTRY macro has three parameters:

A complete list of valid Automation tags is located in the Visual C++ online documentation in the COM and ActiveX folder. You will find one good source by searching for the document titled "VARIANT and VARIANTARG."

Using Ambient Properties with ATL

You can retrieve ambient properties by calling functions implemented in the CComControlBase class. Table 31.2 lists three of the most commonly used ambient properties and the functions you use to access them.

Table 31.2. Commonly Used Ambient Properties and Access Functions

Property Function
Background Color GetAmbientBackColor
Foreground Color GetAmbientForecolor
Font GetAmbientFont

The online documentation for Visual C++ has more examples of ambient property functions. Go to the index and search for topics that begin with GetAmbient. CComControlBase also has a catchall member function named GetAmbientProperty that will return the value of any ambient property, given its DISPID.

Adding Message and Event Handlers

You can add handlers for messages and events to an ATL project in two ways. The graphical way is to right-click the ATL object's C++ class icon in ClassView and choose Add Windows Message Handler from the pop-up menu. A dialog box appears, as Figure 31.3 shows.

31fig03.gif

Figure 31.3 The New Windows Message and Event Handlers dialog box.

You can easily add handlers for Windows messages by selecting a message from the left column and clicking the Add Handler button.

Another option is to manually add the handlers to the class message map using the MESSAGE_HANDLER macro:

MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)

The MESSAGE_HANDLER macro has two parameters:

If you're handling events from a WM_COMMAND message, you must use the COMMAND_CODE_HANDLER macro:

COMMAND_CODE_HANDLER(LBN_DBLCLICK, OnLbDblClicked)

The COMMAND_CODE_HANDLER macro has two parameters:

You can use either of these macros inside the message map, which begins with the BEGIN_MSG_MAP macro and ends with the END_MSG_MAP macro (see Listing 31.4).

Example 31.4. A Typical ATL Message Map

BEGIN_MSG_MAP(CMyButton)
    MESSAGE_HANDLER(WM_CREATE, OnCreate)
    MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
    COMMAND_CODE_HANDLER(BN_CLICKED, OnClicked)
ALT_MSG_MAP(1)
    //  Alternate window message handling
END_MSG_MAP()

Note that the message map in Listing 31.4 also contains an ALT_MAP macro. This marks the beginning of an alternate message map that handle messages from another window within a single message map.

Share ThisShare This

Informit Network