Полезная информация

Chapter 6
Using MFC to Create a Basic ActiveX Control


Using MFC to Create a Basic ActiveX Control

Visual C++ and MFC are powerful and flexible tools for creating ActiveX controls. In terms of rapid development and ease of use, these two win hands down. In this chapter, you will see just how powerful these tools are. You will create an ActiveX control with all the basics: methods, properties, events, persistence, and drawing. And you will explore some of the more advanced features and lesser known aspects of control development, such as methods with optional parameters, asynchronous properties, Clipboard support, and optimized drawing, to name just a few.

NOTE: MFC controls are linked dynamically to the MFC DLLs for support and implementation. Linking dynamically can impair the control's load time because the MFC DLLs are loaded in addition to the control itself. Even though the MFC DLLs may already be in memory, you still have DLL address fix-ups and memory swapping issues. When developing an ActiveX control, the developer is prudent to remove as much dependence on MFC as possible. This practice serves two purposes: First, this practice makes it much easier to port the control to an alternative framework such as ATL or BaseCtl; second, it improves the overall performance of the control because you don't have the overhead of the MFC DLLs to contend with. As a general rule, components that are performance-sensitive should avoid using DLLs of any kind. The last thing you want is a component that is disk-bound.

Creating the Basic Control Project

To create an MFC ActiveX control, you want to take advantage of the AppWizard provided by Visual C++. Run the Visual C++ development environment, and from the File menu select, New. When the New dialog displays (see fig. 6.1), select the Projects tab. The Projects tab allows you the opportunity to define several aspects of how the application will be created, for example, the type of application to create, the name of the application, and the location where you want the project created. For the type, select MFC ActiveX ControlWizard; enter the Project Name MFCControl, and the Location will be C:\que\ActiveX\MFCControl. Click the OK button to start the ControlWizard so you can define the properties of your control.

FIG. 6.1
Define the new MFC control project with the New dialog.

The first step in the ControlWizard allows you to define how many controls will exist within your project (see fig. 6.2). Using the up and down arrow buttons, select 3 for the number of controls, and leave the remainder of the properties set to their default values. Click the Next button to continue.

FIG. 6.2
MFC ActiveX ControlWizard -- Step 1 of 2 is where you define the number of controls in your project.

TIP: If you have a requirement to create more than one control, and the controls are often used together within the same application, we recommend combining the controls into a single .ocx file--because of .ocx load times. A single .ocx always loads faster than two .ocxs of comparable size. Code sharing can also be an advantage, and the end result is a control that's smaller and loads faster than the two individual controls combined.



Step 2 of the ControlWizard allows you to change the name of the control, some of its features, and whether the control will be a subclass of an existing window's control (see fig. 6.3).

FIG. 6.3
Use the ControlWizard -- Step 2 of 2 to further define your control.

Because you have instructed the Control Wizard that you want 3 controls for this project, you will rename MFCControl1, MFCControl2, and MFCControl3 to MFCControlWin, MFCControlNoWin, and MFCControlSubWin, respectively. To rename a class, select the name in the drop-down list box and click the Edit Names button. Enter the new name of the control in the Short Name edit box (see fig. 6.4). The names of the other files and classes will change automatically to conform to the new Short Name. Click OK to close the Edit Names dialog.

FIG. 6.4
Edit the names of the control using the Edit Names dialog.

The Advanced button is used to display a dialog for selecting the ActiveX enhancements that you want for the control (see fig. 6.5). Ensure that the MFCControlWin class is selected, and click the Advanced button.

FIG. 6.5
Select from the Advanced ActiveX Features dialog for your control.

All of the advanced features in the dialog are the result of the OC 96 specification referred to in Chapter 1. Now take a minute to look at the features in detail, as shown in Table 6.1.
Table 6.1 Advanced ActiveX Features
ActiveX Feature Description
Windowless activation Allows the control to be instantiated without having to create a window for itself--relies on the container to do so.
Unclipped device context Used for controls that guarantee they will not draw outside their client area and results in disabling MFC clipping tests for the control. Cannot be used with a windowless control.
Flicker-free activation Used for controls that appear the same to the user whether the control is in an active or inactive state. Selecting this feature eliminates some of the flicker associated with changing states between the active and inactive states of a control. Cannot be used with a windowless control.
Mouse pointer notifications when inactive Enables control to receive mouse messages, even though the control is in an inactive state.
Optimized drawing code Allows the control to draw its User Interface in an optimized fashion if the container supports optimized drawing; otherwise, the control must be drawn using standard drawing techniques.
Loads properties asynchronously Asynchronous properties are properties that load in thebackground and involve the transfer of a large amount of data. The properties are loaded in the background to prevent the container and the control from being disabled or inactive for too long while waiting for the data to load.


From the Advanced ActiveX Features dialog, select only the following options (refer to fig. 6.5):

Click OK to confirm the selection and close the dialog.

Select the MFCControlNoWin control, and click the Advanced button again. You are going to implement MFCControlNoWin as a windowless control just to get a feel for the difference between the windowed and windowless control implementations in MFC. From the Advanced ActiveX Features dialog, select only the following options (refer to fig. 6.5):

Click OK to confirm the selections and close the dialog.

For your third and final control, you subclass an existing window's control. Be sure to select the control MFCControlSubWin, and in the subclassing list box, select BUTTON. Do not define any advanced ActiveX features for this control.

The MFC ActiveX ControlWizard -- Step 2 of 2 dialog also provides other features for further defining your control project. For the sample project, leave the values in their default state. See the VC++ documentation for more information regarding other available options.

Click the Finish button on the ControlWizard dialog to complete the feature selection process (refer to fig. 6.3). The New Project Information dialog displays to let you confirm the choices you made (see fig. 6.6). Click OK to generate the source files and the MFC control project.

FIG. 6.6
Make your confirmations in the New Project Information dialog.

The first step in any control project is to ensure that it contains registration support. Without registration, the control cannot be used by any application.

Control Registration

Control registration and unregistration support is provided for you by MFC. You are not required to make any code changes or additions to support it. Listing 6.1 shows the UpdateRegistry function that was created for the control by the AppWizard; each control will have its own registration function.

One thing of interest is the comment from Microsoft regarding apartment model threading and how you should register your control if you know it doesn't conform to apartment model rules. Other than that, you will not have to make any changes to your application or this function when it comes to control registration.

Listing 6.1 MFCCONTROLWINCTL.CPP--Default UpdateRegistry Implementation

/////////////////////////////////////////////////////////////////////////////
// CMFCControlWinCtrl::CMFCControlWinCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CMFCControlWinCtrl
BOOLCMFCControlWinCtrl::CMFCControlWinCtrlFactory::UpdateRegistry(BOOLbRegister) {
// TODO: Verify that your control follows apartment-model threading rules.
// Refer to MFC TechNote 64 for more information.
// If your control does not conform to the apartment-model rules, then
// you must modify the code below, changing the 6th parameter from
// afxRegApartmentThreading to 0.
if (bRegister)
return AfxOleRegisterControlClass(
AfxGetInstanceHandle(),
m_clsid,
m_lpszProgID,
IDS_MFCCONTROLWIN,
IDB_MFCCONTROLWIN,
afxRegApartmentThreading,
_dwMFCControlWinOleMisc,
_tlid,
_wVerMajor,
_wVerMinor);
else
return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}

You can now compile and register the control you've created, but it won't be of much use because it doesn't contain methods, properties, or events, which are the basis of every ActiveX Control.

Creating Methods

Now that you've successfully created your ActiveX control project, you can start off by adding a method, which is one of the basic aspects of component development.

For the purposes of the sample control, you are going to add a method called CaptionMethod. The method will accept two parameters, the second one being optional. The first parameter is a string that the control will display within its client area, and the second, optional parameter is the alignment of the caption within the client area, either left, right, or center. From the View menu, select ClassWizard, and in the MFC ClassWizard dialog (see fig. 6.7), select the Automation tab.

FIG. 6.7
Adding a method is done through the MFC ClassWizard.

From the Class Name drop-down list box, select the CMFCControlWinCtrl class, and click the Add Method button to create a new method. Type CaptionMethod in the External Name combo box (see fig. 6.8), and set the Return type to a long. Add two parameters--by clicking the appropriate column in the Parameter List control--the first called lpctstrCaption of type LPCTSTR, and the second, as your optional parameter, called varAlignment of type VARIANT. Click OK to add the method to the class. Next click OK to close the ClassWizard dialog.

FIG. 6.8
Add the CaptionMethod method to the control class.

NOTE: All optional parameters must be of type VARIANT, and they must fall at the end of the parameter list. Optional parameters are not managed in any way by OLE. It is the server application's responsibility to determine whether the VARIANT parameter passed to the method contains data and



To aid your CaptionMethod implementation, you need to add an enumeration for all the valid alignment settings and two member variables to your class definition (see Listing 6.2). The enumeration is included in the header file Alignmentenums.h (see Listing 6.3). The two member variables, m_cstrCaption and m_lAlignment, are used to store the caption string and the alignment setting while the control is being used.

Note the type used for the m_lAlignment member variable. The variable is declared as type long and not as the enumeration type because of the data type restrictions that are imposed upon you by ActiveX Automation. Remember that only data types that can be passed in a VARIANT can be used in methods and properties. By declaring the m_lAlignment member as long, you do not have to explicitly convert the value by casting to the enumerated type when it is retrieved from the VARIANT parameter in the caption method. On the other hand, casting the value to the enumerated type is a trivial issue and is completely up to you to implement if you desire to do so.

Listing 6.2 MFCCONTROLWINCTL.H--Alignment Enumeration Include File and Member Variables Added to Class Definition

. . .
// MFCControlWinCtl.h : Declaration oftheCMFCControlWinCtrlActiveXControl
#include "alignmentenums.h"
/////////////////////////////////////////////////////////////////////////////
// CMFCControlWinCtrl : See MFCControlWinCtl.cpp for implementation.
class CMFCControlWinCtrl : public COleControl
{
. . .
protected:
// storage variable for the caption
CString m_cstrCaption;
// storage variable for the alignment
long m_lAlignment;
};

. . .

The enumeration is added as an include file (see Listing 6.3) so that it can be referenced by other files, which will be necessary as you proceed through the chapter.

Listing 6.3 ALIGNMENTENUMS.H--Alignment Enumeration Include File

#if !defined _ALIGNMENTENUMS_H
#define _ALIGNMENTENUMS_H
// caption alignment enumeration
typedef enum tagAlignmentEnum
{
EALIGN_LEFT = 0,
EALIGN_CENTER = 1,
EALIGN_RIGHT = 2,
}EALIGNMENT;

#endif // #if !defined _ALIGNMENTENUMS_H

Open the MFCCOntrolWinCtl.cpp file, and in the constructor initialize the m_Alignment member variable to the value EALIGN_LEFT (see Listing 6.4).

Listing 6.4 MFCCONTROLWINCTL.CPP--Initialize the m_lAlignment Member Variable in the Class Constructor

/////////////////////////////////////////////////////////////////////////////
// CMFCControlWinCtrl::CMFCControlWinCtrl - Constructor
CMFCControlWinCtrl::CMFCControlWinCtrl()
{
InitializeIIDs(&IID_DMFCControlWin, &IID_DMFCControlWinEvents);
m_lReadyState = READYSTATE_LOADING;
// TODO: Call InternalSetReadyState when the readystate changes.
// set the alignment to the default of left
m_lAlignment = EALIGN_LEFT;
}

The CaptionMethod contains all of the code for setting the caption and the alignment style (see Listing 6.5) and does several things of interest that need to be pointed out.

First the method tries to deal with the optional parameter by looking for the data type you really want, VT_I4, which is a data type of long.

If the VARIANT parameter doesn't contain a long, the method then checks to see if any data was passed at all, by checking for VT_ERROR or VT_EMPTY. This is necessary because the parameter is optional. A variant parameter can either contain data or not. It is important to check VARIANT data types not only for valid data but also for the actual existence of data. If no data was supplied, the method relies on the value already contained in the m_lAligmnent class member variable.

Automation Controllers and Optional Parameters
Some automation controllers, such as Visual Basic, do not require that any or all of the optional parameters be supplied when calling a method, for example, the CaptionMethod can be called as




MyObject.CaptionMethod "Hello"

or




MyObject.CaptionMethod "Hello", True

from Visual Basic. Visual Basic automatically passes in aVARIANT set to the type VT_ERROR for any parameters that are omitted from a method call. For automation controllers that call the standard OLE Invoke method, such as C++, all the parameters must be defined, even though they may not be used. In this case, when calling the method, the programmer has the option of setting the VARIANT type to VT_ERROR or VT_EMPTY.

When processing optional parameters, relying on the OLE VARIANT conversion routines in addition to looking for specific VARIANT data types guarantees that your control can handle any data passed to it.



If the VARIANT is of a valid data type other than VT_I4, the method tries to convert it to a VT_I4 type. This is for cases where a user passes valid data in the form of a different data type, for example, a short or a string.

One thing to note is the use of the function VariantInit. It is very important that all VARIANT variables be initialized prior to their use. This practice will guarantee that the VARIANT does not contain invalid data type information or invalid values. This follows the basic C++ tenet of initializing all member variables to ensure that they do not contain invalid information.

If the requirements of your control demand that you deal with only specific data types, you can also add code (error messages, exceptions, and so on) to deal with the fact that the method did not receive a valid data type. If the function VariantChangeType was unable to convert the data, the method exits and returns a value of FALSE. A return of FALSE indicates to the caller of the method that the method didn't succeed. Again, you can add additional error handling code to the method to give the user more information about the error that occurred. See Chapters 3 through 5 on generating OLE exceptions for more information.

Before proceeding, the method ensures that the m_lAlignment member variable contains valid data.

If the method received valid data, or converted the data to a valid value, as indicated by the variable lResult equaling TRUE, it stores the caption and the alignment values in the class member variables, invalidates the control so it will redraw its User Interface (UI) based on the new information, and exits the function.

Listing 6.5 MFCCONTROLWINCTL.CPP--CaptionMethod Implementation

long CMFCControlWinCtrl::CaptionMethod(LPCTSTR lpctstrCaption, const VARIANT FAR& varAlignment)
{
// return value initialized to failure result
long lResult = FALSE;
// if the variant is a long just use the value
if(VT_I4 == varAlignment.vt)
{
// assign the value to our member variable
m_lAlignment = varAlignment.lVal;
// set the return value
lResult = TRUE;
}
// if the user didn't supply an alignment parameter we will use whatever is already there
else if(VT_ERROR == varAlignment.vt || VT_EMPTY == varAlignment.vt)
{
// set the return value
lResult = TRUE;
}
else
{
// get a variant that we can use for conversion purposes
VARIANT varConvertedValue;
// initialize the variant
::VariantInit(&varConvertedValue);
// see if we can convert the data type to something useful - VariantChangeTypeEx() could also be used
if(S_OK == ::VariantChangeType(&varConvertedValue, (VARIANT *) &varAlignment, 0, VT_I4))
{
// assign the value to our member variable
switch(varConvertedValue.lVal)
{
case EALIGN_CENTER:
m_lAlignment = EALIGN_CENTER;
break;
case EALIGN_RIGHT:
m_lAlignment = EALIGN_RIGHT;
break;
default:
m_lAlignment = EALIGN_LEFT;
break;
}
// set the return value
lResult = TRUE;
}
else
{
// at this point we could either throw an error indicating there was a problem converting
// the data or change the return type of the method and return the HRESULT value from the
// the "VariantChangeType" call.
}
}
// if the alignment value is invalid
if(m_lAlignment < EALIGN_LEFT || m_lAlignment > EALIGN_RIGHT)
// set to the default value
m_lAlignment = EALIGN_LEFT;
// if everything was OK
if(TRUE == lResult)
{
// if we have a string
if(lpctstrCaption != NULL)
// assign the string to our member variable
m_cstrCaption = lpctstrCaption;

// did they pass us bad data?
if(m_lAlignment < EALIGN_LEFT || m_lAlignment > EALIGN_RIGHT)
// sure did, lets fix their little red wagon
m_lAlignment = EALIGN_LEFT;
// force the control to repaint itself
this->InvalidateControl();
}
// return the result of the function call
return lResult;
}

To complete your implementation of the CaptionMethod, you need to modify the ODL file. When methods, properties, and events are added to a class with the ControlWizard, the ODL file is also updated with the new entries. The ODL file is compiled into a type library, which is essentially a description of the component and its interfaces that can be used by other applications to access the component. You need to add the keyword [optional] to the last parameter in the CaptionMethod as in Listing 6.6. Doing so lets the container application know that the last parameter is optional and should be dealt with accordingly.

NOTE: Take care when modifying the ODL file by hand. The ODL file must match your implemen- tation exactly. If the file is changed incorrectly, it can introduce strange bugs and/or implementation problems with your component.

The ClassWizard automatically updates the ODL file when methods, properties, and events are added or removed. Adding or removing information from the ODL could prevent the ClassWizard from correctly managing the file.

Keywords such as [optional] do not affect the ClassWizard and its capability to automatically update the file when changes are made to a class.

Listing 6.6 MFCCONTROL.ODL--Keyword [optional] Added to the CaptionMethod ODL Definition

methods:
// NOTE - ClassWizard will maintain method information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_METHOD(CMFCControlWinCtrl)
[id(1)] long CaptionMethod(BSTR lpctstrCaption, [optional] VARIANT varAlignment);
//}}AFX_ODL_METHOD

Properties

Properties can be categorized as user defined, stock, or ambient.

User defined properties are properties that are implementation-specific and have meaning only to the component that contains them. User defined properties can be further broken into those properties that are defined only as their specific data type (normal properties) and those with additional parameters (parameterized properties).

Stock properties are a set of properties that are already defined by OLE in terms of the basic meaning. Stock properties are not implemented in the control of the container by default. They still require implementation by the control developer. They are predefined only to imply a certain level of uniformity between various control implementations.

Ambient properties, on the other hand, are properties that are supported by the container to provide a default value to the control that uses them.

In the remainder of the chapter, you will create three types of properties: normal, parameterized, and stock. You will also learn how to use ambient properties.

Creating Normal User Defined Properties

A normal property is a property that is declared as a single type, for example, long or BSTR, and has no parameters. You will expose your controls' Alignment member variable through a property.

Properties are added in much the same way as methods. Open the ClassWizard, select the class CMFCControlWinCtrl, switch to the Automation tab, and click the Add Property button.

In the Add Property dialog (see fig. 6.9), set the External name to Alignment, the Type to long, and the Implementation to Get/Set methods. Click OK to confirm the entry and close the dialog.

FIG. 6.9
Add the Alignment property to the Class Definition.

Double-click the Alignment property entry in the External name list box to open the source file so you can add your code to the Get/Set methods.

As you can see, Listing 6.7 takes advantage of the member variable m_Alignment, which you added earlier, and uses it to get and set the property value.

The GetAlignment function is simple in that it returns only the value stored in the m_lAlignment member variable.

The SetAlignment function does a little more. This function checks to see if the value is within the valid ranges of values and, if so, stores the value in the m_lAlignment member variable. The function then calls the SetModifiedFlag and the BoundPropertyChanged functions to notify the control and the container, respectively, that the value of the property has changed. BoundPropertyChanged has the effect of forcing the container to refresh its property browser to reflect the new value. This is very important because the value of the property could change without the container's knowledge, either through the control's property sheet or, in some cases, in response to another function call. You might be asking yourself, "Why didn't I add BoundPropertyChanged to the CaptionMethod?" Well, you could have, but it wouldn't do much because the CaptionMethod can never be executed while the control is in design mode, which is the purpose of BoundPropertyChanged.

The last thing the SetAlignment method does is invalidate the control's UI so it will repaint using the new information.

NOTE: Even though a property appears as a single member of an object, it is really a pair of related functions used to operate on a single piece of data. The ODL entries for a property will differ depending on its implementation.

For interfaces inherited from a dispinterface class, it is enough to have a single entry in the properties section of the class. IDispatch inherited interfaces will define two separate functions: one declared as propertyget, and the other defined as propertyput, both sharing the same dispid. Parameterized properties are always defined by using the latter method because it is impossible to define the additional parameters without a method declaration.

The dispinterface keyword is a specific convention for defining IDispatch-based interfaces in a more C++-like fashion, as opposed to the standard IDispatch style, which would be entered as one variable entry versus two method entries. For more information regarding the use of dispinterface and other ODL keywords, see the ODL documentation provided with the VC++ compiler.

Listing 6.7 MFCCONTROLWINCTL.CPP--Alignment Property Get/Set Method Implementation

long CMFCControlWinCtrl::GetAlignment()
{
// return our current setting
return m_lAlignment;
}
void CMFCControlWinCtrl::SetAlignment(long nNewValue)
{
// if we are in the valid range for the property
if(nNewValue >= EALIGN_LEFT && nNewValue <= EALIGN_RIGHT)
{
// set the new property value
m_lAlignment = nNewValue;
// let the control know that the property has changed
this->SetModifiedFlag();
// refresh the property browser
this->BoundPropertyChanged(dispidAlignment);
// redraw the control
this->InvalidateControl();
}
}

MFC and the ClassWizard provide another option when declaring properties-- that is, by member variable. MFC will add a member variable of the appropriate type to the class declaration and will provide a notification function if the data changes. The notification message is defined as Onvariable_nameChanged and is added to the source file of the control. If you had chosen this method for the Alignment member, the function would have been called OnAlignmentChanged and would have been implemented in the MFCControlWinCtl.cpp file. The variable style results in less code to write, but it also results in less flexibility because MFC manages all of the Get/Set property code for you. Feel free to experiment with both methods of property creation to get a feel for your best option.

Creating Parameterized User Defined Properties

A parameterized property is a property that, in addition to being of a specific type (for example, BSTR or long), accepts one or more additional parameters to further define the data of the property. Parameterized properties can be useful for properties that represent collections of data where the additional parameter is the index into the collection.

You are going to expose the controls m_cstrCaption member variable as a parameterized property in addition to your CaptionMethod function.

In the Add Property dialog (see fig. 6.10), define the External name as CaptionProp, its Type as BSTR, and its Implementation as Get/Set methods. In addition, add a parameter called varAlignment of type VARIANT to the Parameter list box. Click OK to add the property to the class. Close the ClassWizard so you can add some code to your property.

FIG. 6.10
Add the Caption property with the Add Property dialog.

The implementation of the property CaptionProp has two methods: GetCaptionProp and SetCaptionProp.

GetCaptionProp is the method that is called to return data from the property. In your implementation, you ignore the alignment parameter because it is of no use to you in this context; you simply return the caption (see Listing 6.8). GetCaptionProp uses the CString function AllocSysString to create a BSTR, which is essentially a UNICODE string, that is returned from the function call.

Listing 6.8 MFCCONTROLWINCTL.CPP--GetCaptionProp Implementation

BSTR CMFCControlWinCtrl::GetCaptionProp(const VARIANT FAR& varAlignment)
{
// return the caption as a BSTR
return m_cstrCaption.AllocSysString();
}

SetCaptionProp simply defers to the CaptionMethod implementation because the CaptionMethod already does everything that you need (see Listing 6.9).

Listing 6.9 MFCCONTROLWINCTL.CPP--SetCaptionProp Implementation

void CMFCControlWinCtrl::SetCaptionProp(const VARIANT FAR& varAlignment, LPCTSTR lpszNewValue)
{
// use the "CaptionMethod" implementation
if(TRUE == this->CaptionMethod(lpszNewValue, varAlignment))
// let the container know that the property has changed
this->SetModifiedFlag();
}

Your final detail is the ODL file (see Listing 6.10). You are going to make the VARIANT, varAlignment, an optional parameter for the GetCaptionProp and SetCaptionProp implementations. You can make the varAlignment parameter optional for the SetCaptionProp implementation because the varAlignment variable is the last in the list of actual parameters. The BSTR lpszNewValue variable is actually the data type of the property function pairs and does not affect the parameter definition rules.

Listing 6.10 MFCCONTROL.ODL--[optional] Keyword Added to the ODL File

methods:
// NOTE - ClassWizard will maintain method information here. // Use extreme caution when editing this section.
//{{AFX_ODL_METHOD(CMFCControlWinCtrl)
[id(2)] long CaptionMethod(BSTR lpctstrCaption, [optional] VARIANT varAlignment);
[id(3), propget] BSTR CaptionProp([optional] VARIANT varAlignment);
[id(3), propput] void CaptionProp([optional] VARIANT varAlignment, BSTR lpszNewValue);
//}}AFX_ODL_METHOD

NOTE: Because of the parameters they contain, parameterized properties are defined in the methods section of the dispinterface declaration of an object. All properties are implemented using the dual method style in IDispatch-based interfaces regardless of whether the property is parameterized or not. Only dispinterfaces contain a separate property and method section in the ODL. And only dispinterfaces allow for properties to be declared as a single line entry in the ODL file.

Creating Stock Properties

A stock property is a property that is understood by both a control and its container and that has a predefined meaning to both. Stock properties are intended to provide basic uniform functionality to all the controls and containers that implement them. Stock properties do not require you to implement a lot of code; you just hook into the existing property.

Adding stock properties is similar to adding user defined properties. Open the ClassWizard, select the Automation tab, select the class CMFCControlWinCtrl, and click the Add Property button. Instead of typing an External name this time, you select one from the list provided. Your stock property will be the BackColor property (see fig. 6.11). Select the Stock Implementation, and click OK to add the property to the class.

FIG. 6.11
Add BackColor stock property to the class definition.

That's all there is to it. The BackColor property now appears in your property list and will persist with all your other properties. Whenever you need the value of a stock property, you can query the container for the property with one of the stock property functions. In your case, you will use the function GetBackColor. All of the stock properties have an associated Get/Set function pair. See the MFC documentation for more information.

In addition to the default stock implementation style, you can support a member variable implementation. This option eliminates the need to query the container for the property but forces you to carry the value with you at all times, adding to your instance size.

You can also support the Get/Set implementation method and store or use the value in anyway you see fit, but this option also requires additional code.

The default stock implementation is obviously the easiest because MFC does all the work for you.

Using Ambient Properties

Ambient properties are properties implemented in the container in which the control resides, as opposed to stock properties, which are implemented in the control and not the container. Ambient properties share the same set of predefined meanings and dispids as those of stock properties. A dispid is the unique identifier that is used to identify the property within an interface. To use an ambient property, the control need request only the property value from the container and apply it in whatever manner is appropriate for the property type. The use of ambient properties allows the control to conform to the same settings as those of the container in which it resides. This provides much better integration between the control and its container.

Take the previous example of adding the BackColor stock property to the sample control implementation. Defined as a stock property, the user of the control can change the background color of the control or leave it as is. If the color is different from that of the container or if the container's background color changes for some reason, the colors won't match, giving the appearance of a poorly integrated and written application. However, if the control simply used the ambient background color of its container, the control's background will always match that of the container. The specific requirements of your control implementation will decide which route you choose when implementing the properties your control supports.

To access an ambient property, you can call one of the many ambient property functions defined in the COleControl class, for example, AmbientBackColor(), or use the function

this->GetAmbientProperty(DISPID_BACKCOLOR, VT_COLOR, &varBackColor);

to access the value. The GetAmbientProperty() function takes a dispid as its first parameter. The dispid must be one of those defined by MFC. A complete list of dispids can be found in the MFC source files. The second parameter is the VARIANT data type that is being requested, and the third parameter is a VARIANT variable into which the data is stored.

Creating Property Sheets

Property sheets are a way for a control to display its properties for review and editing and are typically implemented in a tabbed dialog format. The original intent of property sheets was for use in cases when a control container did not support property browsing facilities. While property sheets have their purpose, they are probably not necessary for all implementations.

Removing the property sheets from your control implementation definitely reduces the size of your control and does not take away from its implementation.

Because property sheets are tabbed dialogs, most of your work will be done with the dialog editor and the ClassWizard. Select the Resource View in the Project Workspace window. From the list of dialogs, select IDD_PROPPAGE_MFCCONTROLWIN, and double-click the entry to open the resource editor.

Using the resource editor, remove the static text control with the caption TODO, and place a static text control and a combo box control on the dialog (see fig. 6.12).

FIG. 6.12
Use the resource editor to add controls to the Property Sheet dialog.

Using the mouse, select the label control on the form, and click the right mouse button. In the menu that appears, select the Properties menu item. On the General tab, set the ID of the control to IDC_ALIGNMENTLABEL and set the Caption to Alignment. Select the Styles tab and set the Align Text property to Right. Close the dialog to save the information.

Again, using the mouse, select the combo box, use the right mouse to click on the control, and in the menu that appears, select the Properties menu item.

On the General tab, set the ID of the control to IDC_ALIGNMENTCOMBO.

On the Styles tab, set the T_ype to drop-down list box, and uncheck the Sort check box. Close the dialog to save the information.

You have placed your two controls onto the property sheets and successfully modified their properties. Now you need to add some code to complete the implementation. Close the resource editor, and open the ClassWizard. Select the CMFCControlWinPropPage class, and select the Member Variables tab. In the Control ID's combo box, select IDC_ALIGNMENTCOMBO, and click the Add Variable button.

In the Add Member Variable dialog (see fig. 6.13), set the Member variable name to m_AlignmentCombo and the Category to Control. Click OK to confirm the information, and close the dialog.

FIG. 6.13
Add the m_AlignmentCombo member variable to the class definition.

You've added a member variable for the control. Now you add one for the data. Click the Add Variable button again, and set the Member variable name to m_AlignmentValue (see fig. 6.14). Set the Category to Value and the Variable type to int. Click OK to confirm the changes, and close the dialog.

FIG. 6.14
Add the m_AlignmentValue member variable to the class definition.

Close the MFC ClassWizard dialog to confirm the new member variables. DoDataExchange is where you are going to add the code to manage the data between the control and the property sheet. Open the source file MFCControlWinPpg.cpp, and locate the DoDataExchange function. Notice that the ClassWizard added two lines of code to the original implementation of DoDataExchange (see Listing 6.11).

Listing 6.11 MFCCONTROLWINPPG.CPP--New Member Variables Added to the DoDataExchange Function

void CMFCControlWinPropPage::DoDataExchange(CDataExchange* pDX)
{
//{{AFX_DATA_MAP(CMFCControlWinPropPage)
DDX_Control(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentCombo);
DDX_CBIndex(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentValue);
//}}AFX_DATA_MAP
DDP_PostProcessing(pDX);
}

DDX_Control is the standard MFC macro for loading a control into an MFC class member. DDX_CBIndex is a standard MFC function for getting and setting the current index in a combo box using the variable supplied, in this case m_AlignmentValue. You need to change this implementation slightly to fully bind your control and property sheet (see Listing 6.12).

First you load the combo box with valid property data so that the property sheet matches the property values in the control and the property browser. The function loads the combo box with only the valid choices if the DoDataExchange function is being executed the first time by checking the m_bSaveAndValidate member variable; in other words, the property dialog is not saving and validating the data it contains.

To support communication between the control and the property sheet, you need to add DDP_CBIndex before the DDX_CBIndex line. DDP_CBIndex instructs MFC to obtain the Alignment property value from the control and store it in the m_lAlignment member variable of the property dialog when the dialog is loading. When the property dialog is unloading, DDP_CBIndex retrieves the current index of the combo box and sets the Alignment property of the control to the new value. DDP_CBIndex must be before the DDX_CBIndex function; otherwise, the property sheet will not correctly reflect the values of the properties in the control.

NOTE: Because of the specific implementation requirements of this property dialog (that is, the list box control is being loaded into the member variable, m_AlignmentCombo, of the class, and it is necessary to load the combo box selection list prior to setting the current selected entry in the list), the DDP_CBIndex and DDX_CBIndex lines had to be removed from between the MFC AFX_DATA_MAP macros to allow the setting of the entries to occur. Separating the lines in this fashion is not required in order to implement the DDX_CBIndex and DDP_CBIndex lines. It is, however, the only way to solve this particular problem.

When the DDX_CBIndex and DDPCBIndex lines are removed from between the MFC macros, they no longer appear as a member variable in the ClassWizard and are not managed automatically by VC++ and the ClassWizard.

Listing 6.12 MFCCONTROLWINPPG.CPP--Updated DoDataExchange Function; Alignment Enumeration Added

void CMFCControlWinPropPage::DoDataExchange(CDataExchange* pDX)
{
//{{AFX_DATA_MAP(CMFCControlWinPropPage)
DDX_Control(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentCombo);
//}}AFX_DATA_MAP
if(!pDX->m_bSaveAndValidate)
{
// make sure that we have cleared the list
m_AlignmentCombo.ResetContent();
m_AlignmentCombo.AddString(_T("Left"));
m_AlignmentCombo.AddString(_T("Center"));
m_AlignmentCombo.AddString(_T("Right"));
}
DDP_CBIndex(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentValue, _T("Alignment"));
DDX_CBIndex(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentValue);
DDP_PostProcessing(pDX);
this->SetModifiedFlag();
}

As was pointed out earlier in this chapter, in order for the property browser within the IDE (for example, Microsoft Visual Basic's property list window) to accurately reflect the property value after it has been changed by the Property Sheet, you must add a BoundPropertyChanged call to your SetAlignment function (see Listing 6.13). This action notifies the property browser that the value has changed and that it should be retrieved again.

Listing 6.13 MFCCONTROLWINCTL.CPP--BoundPropertyChanged Function within the SetAlignment Implementation

void CMFCControlWinCtrl::SetAlignment(long nNewValue)
{
// if we are in the valid range for the property
if(nNewValue >= EALIGN_LEFT && nNewValue <= EALIGN_RIGHT)
{
// set the new property value
m_lAlignment = nNewValue;
// let the control know that the property has changed
this->SetModifiedFlag();
// refresh the property browser
this->BoundPropertyChanged(dispidAlignment);
// redraw the control
this->InvalidateControl();
}
}

The only thing remaining is to set the default value of the member variable m_AlignmentValue to a meaningful value (see Listing 6.14). Add the include file entry

#include :MFCControlWinCtl.h"

to the source file to resolve the EALIGN_LEFT constant. Initialize the m_AlignmentValue member variable in the constructor to a value of EALIGN_LEFT.

Listing 6.14 MFCCONTROLWINPPG.CPP--m_AlignmentValue Class Member Initialized in the Class Constructor

// MFCControlWinPpg.cpp : Implementation of the CMFCControlWinPropPage property page class.
#include "stdafx.h"
#include "MFCControl.h"
#include "MFCControlWinCtl.h"
#include "MFCControlWinPpg.h"
. . .
CMFCControlWinPropPage::CMFCControlWinPropPage() :
COlePropertyPage(IDD, IDS_MFCCONTROLWIN_PPG_CAPTION)
{
//{{AFX_DATA_INIT(CMFCControlWinPropPage)
m_AlignmentValue = EALIGN_LEFT;
//}}AFX_DATA_INIT
}

Adding Events

Properties and methods are a way for a programmer to communicate with a control from within the container application. Events are a way for the control to communicate with the container. For ActiveX controls, events are nothing more than IDispatch interfaces that are implemented on the container side of the container/control relationship. The mechanism that events are based on is known as a connection point. A connection point is simply a description of the type of interface that is required in order to communicate with the container. Connection points are not restricted to only IDispatch interfaces, but rather they can be of any COM interface that is understood by both components. For that matter, connection points/events are not restricted only to controls, they can be used in any COM implementation. Controls were simply the first to take advantage of them. For more information regarding connection points, refer to the documentation in the OLE online help or to Kraig Brockschmidt's Inside OLE, Second Edition, from Microsoft Press.

As with methods and properties, events are added and removed with the ClassWizard. Open the ClassWizard, and select the ActiveX Events tab. Select the CMFCControlWinCtrl class, and click the Add Event button.

You can choose from a list of predefined events or add your own. You are going to add a Change event (see fig. 6.15), and it will have two parameters. The first parameter will be a string, passed by reference (BSTR *), called cstrCaption. The second will be a long, also passed by reference (long *), called lAlignment. You are passing the parameters by reference to allow the container application the opportunity to change the values if necessary.

Click OK to add the event to the class. Before you use your event, you are going to create a common FireChange function that will encapsulate the code that deals with the cases where the program has changed the data passed to the event. Close the MFC ClassWizard dialog to confirm the addition of the event to the class.

You need to add your function prototype to the class definition in the header file (see Listing 6.15). And you need to add the implementation to the source file (see Listing 6.16).

FIG. 6.15
Here is where you add the Change event.

Listing 6.15 MFCCONTROLWINCTL.H--FireChange Function Prototype Added to the Class Definition

. . .
protected:
// storage variable for the caption
CString m_cstrCaption;
// storage variable for the alignment
long m_lAlignment;
void FireChange(void);
};

Listing 6.16 MFCCONTROLWINCTL.CPP--FireChange Implementation Added to the MFControlWinCtl.cpp Source File

void CMFCControlWinCtrl::FireChange(void)
{
// get a BSTR that can be passed via the event
BSTR bstrCaption = m_cstrCaption.AllocSysString();
// fire the change event
this->FireChange(&bstrCaption, &m_lAlignment);
// let's see what the user gave us
m_cstrCaption = bstrCaption;
// free the data that was passed back
::SysFreeString(bstrCaption);
}

NOTE: The control is responsible for the data that is passed in to the event, so be sure to free the BSTR that was passed in when the function has returned to prevent a memory leak.



The common FireChange function allows you to hide the details surrounding the change event from the rest of the program. If you decide in the future to change its implementation, it will impact only one function rather than a number of them.

The CaptionMethod will require that you fire a Change event if the data changes, so you will add your new event there (see Listing 6.17). You also want to add it to the SetAlignment function, but do not add FireChange to the SetCaptionProp function because it defers to the CaptionMethod function. Also, do not forget to add the function to any new functions or features that are added to the control as its implementation progresses.

Listing 6.17 MFCCONTROWINCTL.CPP--FireChange Event Added to the CaptionMethod Implementation

. . .
// if everything was OK
if(TRUE == lResult)
{
// if we have a string
if(lpctstrCaption != NULL)
// assign the string to our member variable
m_cstrCaption = lpctstrCaption;

// did they pass us bad data?
if(m_lAlignment < EALIGN_LEFT || m_lAlignment > EALIGN_RIGHT)
// sure did, lets fix their little red wagon
m_lAlignment = EALIGN_LEFT;
// fire the global change event
this->FireChange();
// force the control to repaint itself
this->InvalidateControl();
}
// return the result of the function call
return lResult;
}

Persistence

Persistence refers to the capability of a component to retain its state across execution lifetimes. In other words, regardless of the number of times that the control is started and stopped, it remembers that you changed its background color from white to mauve (even if it finds the color mauve revolting).

The implementation of persistence, for MFC controls, is a no-brainer consisting of very little code. Simply add a single line to the DoPropExchange method of your control for each one of your properties, and you are finished. You can get a little more fancy and persist the properties differently, depending on whether you are saving or loading the properties, but that isn't real complicated either.

Now modify your DoPropExchange function to persist the two properties, m_lAlignment and m_cstrCaption. The persistence can be implemented two ways. The first, and the simplest, is to add one entry for each property without regard for order or dependencies. Dependencies refers to the fact that as your control implementation gets more complicated or requires a larger number of properties, you may find that how or when certain properties are persisted can depend on other properties.

TIP: When specifying the default value in your persistence entries, never use the same variable as the one that is going to receive the data. The MFC persistence functions are optimized so that if the value being persisted has not changed compared to the default value, it will not be persisted. This is to save time and space when loading or saving the persistence information. If you use the same variable, MFC will think that the value has never changed because the MFC persistence routines perform simple address and memory comparisons. Use constants for your default persistence information because that is what the routines expect.



Listing 6.18 demonstrates the simplest way to persist the properties. The same method is used for each property regardless of the direction of the persistence; when you are loading or saving, MFC handles all those details for you.

Listing 6.18 MFCCONTROLWINCTL.CPP--Simple DoPropExchange Implementation

void CMFCControlWinCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
// TODO: Call PX_ functions for each persistent custom property.
PX_Long(pPX, _T("Alignment"), m_lAlignment, EALIGN_LEFT);
PX_String(pPX, _T("CaptionProp"), m_cstrCaption, _T(""));
}

Listing 6.19 demonstrates how you would implement your persistence if you wanted to load your properties first and then perform some implementation specific action--and conversely when saving the properties. The requirement to specifically order the persistence process is highly subjective and will depend greatly on your implementation. Listing 6.19 merely demonstrates the possibility of doing so.

Listing 6.19 MFCCONTROLWINCTL.CPP--DoPropExchange Implementation Separated into Distinct Operations

void CMFCControlWinCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
// TODO: Call PX_ functions for each persistent custom property.
// if we are loading the properties
if(pPX->IsLoading())
{
PX_Long(pPX, _T("Alignment"), m_lAlignment, EALIGN_LEFT);
PX_String(pPX, _T("CaptionProp"), m_cstrCaption, _T(""));
}
// if we are saving the properties
if(!pPX->IsLoading())
{
PX_Long(pPX, _T("Alignment"), m_lAlignment, EALIGN_LEFT);
PX_String(pPX, _T("CaptionProp"), m_cstrCaption, _T(""));
}
}

TIP: If your control contains stock properties, you must call the base class implementation COleControl::DoPropExchange to support their persistence. The base class implementation does not allow much room for flexibility, though, and you may find yourself in more need of control than is allowed.

One option is to persist the stock properties yourself and ignore the base class implementation. Listing 6.20 shows what code is necessary to persist the BackColor property and supply a different default value. In this case, the default value is the system defined WindowText color and not the ambient color of the container. There is a minor difference between MFC versions 4.1 and 4.2 in how the stock mask is retrieved, but other than that, the remainder of the code is copied verbatim from the MFC source files and has not been altered in any way.

The one major drawback to this is that you are now required to maintain your stock property persistence code by hand and can no longer rely on the base class implementation provided by MFC.

Listing 6.20 EXAMPLE--Example Implementation of the Code Needed to Support Stock Property Persistence

void CSOTABaseCtrl::DoPropExchange(CPropExchange* pPX)
{
// ****** Taken from "void COleControl::DoPropExchange(CPropExchange* pPX)" ctlcore.cpp
// **
ASSERT_POINTER(pPX, CPropExchange);
// **
// ****** Taken from "void COleControl::DoPropExchange(CPropExchange* pPX)"
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
// COleControl::DoPropExchange(pPX);
// ****** Taken from "void COleControl::DoPropExchange(CPropExchange* pPX)" ctlcore.cpp
// **
ExchangeExtent(pPX);
// **
// ****** Taken from "void COleControl::DoPropExchange(CPropExchange* pPX)" ctlcore.cpp
// ****** Taken from "void COleControl::ExchangeStockProps(CPropExchange* pPX)" ctlprop.cpp
// **
BOOL bLoading = pPX->IsLoading();
// VC++ 4.2
DWORD dwPersistMask = GetStockPropMask();
// VC++ 4.1
// DWORD dwPersistMask = m_dwStockPropMask;
PX_ULong(pPX, _T("_StockProps"), dwPersistMask);
if (dwPersistMask & STOCKPROP_BACKCOLOR)
{
if (dwPersistMask & STOCKPROP_BACKCOLOR)
PX_Color(pPX, _T("BackColor"), m_clrBackColor, 0x80000000 | COLOR_WINDOW /*AmbientBackColor()*/);
else if (bLoading)
m_clrBackColor = 0x80000000 | COLOR_WINDOW /*AmbientBackColor()*/
}
// **
// ****** Taken from "void COleControl::ExchangeStockProps(CPropExchange* pPX)" ctlprop.cpp

Drawing the Control

Most controls will have some form of UI. Although since the release of the OC 96 specification and ActiveX, that is no longer a requirement.

Drawing the UI of a control has long been regarded as one of the most critical aspects of a control, in terms of both appearance and performance. A control that flashes or flickers a lot appears poorly developed regardless of its internal implementation. The same is true for how fast the control draws. With the advent of the Internet and ActiveX, it is even more crucial that a control draw fast and draw well.

Drawing can be broken into two major categories: standard and optimized. In this chapter, you focus only on standard drawing. Chapter 7 introduces optimized drawing.

Standard Drawing

Standard drawing is just that: standard. You have complete freedom to draw the control any way you see fit, using any method that is appropriate. You can use pens and brushes, rectangles and circles. Remember that drawing smart is the goal of any application with UI.

TIP: Probably the greatest sources of flicker and flash are overlapped painting and unnecessary drawing--drawing areas of the control that do not need to be drawn. Try to draw only to the areas of the control that have been invalidated. Doing so will save time and will prevent annoying flash. For example, if your control has a white background and a black border, draw only the white portion where it is going to be seen. Don't draw over the border as this causes the control to flash every time it gets a paint message.



Before adding the OnDraw implementation to your source file, add a CBrush member variable to the CMFCControlWinCtrl class (see Listing 6.21). The new member variable will hold a CBrush object and will be used more fully in the optimized drawing section you find in Chapter 7. For now, the CBrush object will be re-created every time the OnDraw function executes.

Listing 6.21 MFCCONTROLWINCTL.H--CBrush Member Variable Added to CMFCControlWinCtrl

protected:
// storage variable for the caption
CString m_cstrCaption;
// storage variable for the alignment
long m_lAlignment;
void FireChange(void);
// brush
CBrush m_Brush;
};

Listing 6.22 contains the code for drawing the control. The OnDraw implementation is fairly straightforward. Select the font into the DC, get all of the colors that you are going to use, draw the background, draw the text, and draw the border. Then reset everything back to the way it was when you started.

Listing 6.22 MFCCONTROLWINCTL.CPP--Standard Drawing Added to the OnDraw Function

void CMFCControlWinCtrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
// ****** Get the text font ******
// **
CFont * pOldFont;

// get the stock font
pOldFont = this->SelectStockFont(pdc);
// **
// ****** Get the text font ******
// ****** Get the colors ******
// **
// use the window color as the background color
COLORREF clrTextBackgroundColor = this->TranslateColor(this- >GetBackColor());
// then use the normal windows color for the text
COLORREF clrTextForegroundColor = this->TranslateColor(this- >GetForeColor());
// set to the system color
COLORREF clrEdgeBackgroundColor = ::GetSysColor(COLOR_3DFACE);
COLORREF clrEdgeForegroundColor = ::GetSysColor(COLOR_3DFACE);
// **
// ****** Get the colors ******
// ****** Draw the background ******
// **
// set the text color
COLORREF clrOldBackgroundColor = pdc->SetBkColor(clrTextBackgroundColor);
COLORREF clrOldForegroundColor = pdc >SetTextColor(clrTextForegroundColor);
// if we don't have a brush
if(m_Brush.m_hObject == NULL)
// create a solid brush
m_Brush.CreateSolidBrush(clrTextBackgroundColor);
// select the brush and save the old one
CBrush * pOldBrush = pdc->SelectObject(&m_Brush);
// draw the background
pdc->Rectangle(&rcBounds);
// **
// ****** Draw the background ******
// ****** Draw the text ******
// **
int iHor, iVer;
// get the size of the text for this DC
CSize oSize = pdc->GetTextExtent(m_cstrCaption);
switch(m_lAlignment)
{
case EALIGN_CENTER:
iHor = (rcBounds.right - oSize.cx) / 2;
iVer = rcBounds.top + 3;
break;
case EALIGN_RIGHT:
iHor = rcBounds.right - oSize.cx - 3;
iVer = rcBounds.top + 3;
break;
// case EALIGN_LEFT:
default:
iHor = rcBounds.left + 3;
iVer = rcBounds.top + 3;
break;
}
// output our text
pdc->ExtTextOut(iHor, iVer, ETO_CLIPPED | ETO_OPAQUE, rcBounds, m_cstrCaption, m_cstrCaption.GetLength(), NULL);
// **
// ****** Draw the text ******
// ****** Draw the border ******
// **
// set the edge style and flags
UINT uiBorderStyle = EDGE_SUNKEN;
UINT uiBorderFlags = BF_RECT;
// set the edge color
pdc->SetBkColor(clrEdgeBackgroundColor);
pdc->SetTextColor(clrEdgeForegroundColor);
// draw the 3D edge
pdc->DrawEdge((LPRECT)(LPCRECT) rcBounds, uiBorderStyle, uiBorderFlags);
// **
// ****** Draw the border ******
// ****** Reset the colors ******
// **
// restore the original colors
pdc->SetBkColor(clrOldBackgroundColor);
pdc->SetTextColor(clrOldForegroundColor);
// **
// ****** Reset the colors ******
// ****** release the text font ******
// **
// set the old font back
pdc->SelectObject(pOldFont);
// **
// ****** Get the text font ******
// select the old brush back
pdc->SelectObject(pOldBrush);
}

From Here...

This chapter focused on creating a basic MFC control implementation. Methods, properties, and events are the basis for any control implementation, and using MFC makes it truly easy to add them in a short period of time. This chapter also addressed the issues of persistence and drawing, which are necessary for a complete control implementation.

MFC was the first tool available from Microsoft for doing ActiveX development--back then it was called OLE. MFC was meant to hide all the details of implementing ActiveX/COM components from you so that you wouldn't have to learn a whole new way of developing MFC applications just to support ActiveX/COM. The fact that MFC hides all of the details is exactly its limitation. You may be able to create ActiveX controls in a short period of time, but their size and performance may not be that desirable. MFC does have a distinct advantage in the area of maturity of integration with the IDE since the IDE seems to be designed around making it as easy as possible to create applications when using MFC.

Chapter 7 expands on the things you learned in this chapter about creating controls. It also shows you how to add new features to your control implementation that can make it truly unique and interesting.