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

Chapter 7
Advanced ActiveX Control Development with MFC


Advanced ActiveX Control Development with MFC

This chapter expands upon the information in Chapter 6 about creating a basic MFC ActiveX control. In addition to the features that you are familiar with, such as Clipboard and Drag and Drop support, you will learn how to implement asynchronous properties and optimized drawings, which are the result of the adoption of OC 96 specification.

Properties

Chapter 6 tells you how to add the various types of properties to your control implementation. One type of property has yet to be examined: asynchronous properties.

Creating Asynchronous Properties

Asynchronous properties are those properties that typically represent a large amount of data, such as a text file or a bitmap, and are loaded as a background process so as not to interfere with the normal processing of the control and the container. This statement can be somewhat misleading. Asynchronous refers only to the call to load the data; it does not refer to the actual loading. For example, a control uses a bitmap as its background and has defined it as an asynchronous property. If OLE determines that the bitmap is already on the local machine, the data is considered to be available to the control and, subsequently, will instruct the control that all of the data is available. If OLE determines that the bitmap is not available on the local machine, OLE will load the data as fast as possible and inform the control as data becomes available. After the data is in a location that is considered accessible, the property essentially behaves as any other property would. If you require the asynchronous loading of the data regardless of its location, you must implement it yourself.

Except for a few changes, asynchronous properties are added in the same fashion as any other property. In Chapter 6, when you initially create your control, you have the opportunity to define some ActiveX features, one being Loads Properties Asynchronously. Choosing this option adds some code to your application that normally would not be implemented.

First, the stock property ReadyState was added to your control. This property is used to notify the container of the state that the control is in while loading its properties. The stock event ReadyStateChange, which is used to notify the container that the ReadyState of the control has changed, was also added. The last thing that was added was the initialization of the member variable m_lReadyState to READYSTATE_LOADING in the controls constructor (see Listing 7.1).

Listing 7.1 MFCONTROLWINCTL.CPP--Constructor Initialization of ReadyState

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;
}

This is the extent of the work that is done for you at the time your project is created.

The control must have an entry point for OLE to communicate with when notifying the control when and how much data is available. This is accomplished through the CDataPathProperty or the CCachedDataPathProperty class.

CDataPathProperty is used for data that typically arrives in a continuous fashion, such as stock market data. CCachedDataPathProperty is used for data that is retrieved once and then stored or cached, such as a file. You must implement a class in your control that is inherited from one of these classes. To add a new class, you use the ClassWizard.

For the purposes of this chapter and the sample application, you will implement an asynchronous property that reads string data from a file and outputs the data as the caption of the control.

Open the ClassWizard, click the Add Class button on any one of the tab pages, and select the New menu item. In the New Class dialog (see fig. 7.1), enter the Name CMyDataPath, select a Base class of CCachedDataPathProperty, and click the OK button to add the new class.

FIG. 7.1
Add a new CCachedDataPath Property class with the ClassWizard.

Your control must override the OnDataAvailable function within the CMyDataPath class in order to receive data as it becomes available. From the open ClassWizard dialog, select the Message Maps tab and locate the OnDataAvailable message within the Messa_ges list box. Double-click the OnDataAvailable message to add the method to your class. Click the OK button to close the ClassWizard.

The first step is to add the cstrMyBuffer member variable to the CMyDataPath class (see Listing 7.2). The member variable, cstrMyBuffer, is used to store all of the data as it is passed to the OnDataAvailable function.

Listing 7.2 MYDATAPATH.H--CString Member Variable Added to the CMyDataPath Class

. . .
// Implementation
protected:
CString cstrMyBuffer;
};

The next step is to update the control class, CMFCControlWinCtrl, to include the header file of the CMyDataPath class and also to add a new member variable, oMyDataPath (see Listing 7.3). The CMyDataPath class also needs access to the function CaptionMethod in the control so that the data can be placed in the control when it is all available. Since the CaptionMethod is a protected function, it is necessary to allow the CMyDataPath class access to the function using the C++ friend declaration.

Listing 7.3 MFCCONTROWINCTL.H--CMFCControlWinCtrl Class Updated to Include the CMyDataPath Class

. . .
#include "alignmentenums.h"
#include "mydatapath.h"
/////////////////////////////////////////////////////////////////////////////
// CMFCControlWinCtrl : See MFCControlWinCtl.cpp for implementation.
. . .
friend class CMyDataPath;
CMyDataPath oMyDataPath;
};
. . .

The next step is to add the code to the OnDataAvailable function in the CMyDataPath class to deal with the data as it is sent to the control. The OnDataAvailable method is straightforward (see Listing 7.4). It has two properties, dwSize and bscfFlag. DwSize is the number of bytes of data that are currently available in this call to the function. BscfFlag is a set of flags indicating the current operation taking place (see Table 7.1).
Table 7.1 BSCF Notifications
Notification Message Description
BSCF_FIRSTDATANOTIFICATION This message is sent the first time that the OnDataAvailable function is called. It is important to note that this message can be sent along with the BSCF_LASTDATANOTIFICATION message.
BSCF_INTERMEDIATEDATANOTIFICATION This message is sent while the OLE is still loading data.
BSCF_LASTDATANOTIFICATION This message is sent when a control has been given all of the data that is available to the property. It is important to note that this message can be sent along with the BSCF_FIRSTDATANOTIFICATION message.


Note the use of the BSCF notification messages and how you must respond to each. Do not process the messages exclusive of each other; you must process each message individually. Remember that you can receive more than one notification message in each call to OnDataAvailable.

The OnDataAvailable implementation first clears the string buffer if this is the first call to the function. Next it creates a buffer of the appropriate size to receive the data from the Read function. If the data was successfully read, the data is added to the member variable cstrMyBuffer. Finally if this is the last call to the OnDataAvailable function, the CaptionMethod is called supplying the new data and an empty variant parameter for the alignment. The last thing you must do is change the state of the control to READYSTATE_COMPLETE, which indicates that all of the asynchronous properties have been loaded.

Listing 7.4 MYDATAPATH.CPP--OnDataAvailable Implementation

void CMyDataPath::OnDataAvailable(DWORD dwSize, DWORD bscfFlag)
{
// if this is the first notification
if(bscfFlag & BSCF_FIRSTDATANOTIFICATION)
// clear the string buffer
cstrMyBuffer.Empty();
CString cstrTempBuffer;
// get a temp buffer
LPTSTR lptstrTempBuffer = cstrTempBuffer.GetBuffer(dwSize);
// read the data to a temp buffer
UINT uiBytesRead = this->Read(lptstrTempBuffer, dwSize);
// if we read in any data
if(uiBytesRead)
{
// store the data
cstrTempBuffer.ReleaseBuffer(uiBytesRead);
cstrMyBuffer += cstrTempBuffer;
}
// if this is our last notification
if(bscfFlag & BSCF_LASTDATANOTIFICATION)
{
VARIANT varAlignment;
::VariantInit(&varAlignment);
varAlignment.vt = VT_EMPTY;
((CMFCControlWinCtrl *) this->GetControl())->CaptionMethod(cstrMyBuffer,
varAlignment);
this->GetControl()->InternalSetReadyState(READYSTATE_COMPLETE);
}
}

Next you need to add an OLE property to store the actual location of the data that is to be loaded asynchronously. Open the ClassWizard, select the Automation tab, select the CMFCControlWinCtrl class, and click the Add Property button. In the Add Property dialog, enter the External name TextData, select the Type BSTR and an Implementation of Get/Set methods. Do not use the OLE_DATAPATH type for this property, as it doesn't work; the type must be a BSTR. Use of the OLE_DATAPATH and its related problems is a known bug with Microsoft and MFC. Click the OK button to close the dialog and add the property to the class. Double-click the TextData entry in the External names list box to close the ClassWizard and open the source file.

To complete your implementation, you need to add some code to the CMFCControlWinCtrl class source file. First update the class constructor to set the control class in the oMyDataPath object (see Listing 7.5). Doing this is absolutely necessary for the DoPropExchange function to work correctly when loading the data path property.

Listing 7.5 MFCCONTROLWINCTL.CPP--oMyDataPath Object Initialized in the CMFCControlWinCtrl Class Constructor

CMFCControlWinCtrl::CMFCControlWinCtrl()
{
InitializeIIDs(&IID_DMFCControlWin, &IID_DMFCControlWinEvents);
// TODO: Call InternalSetReadyState when the readystate changes.
m_lReadyState = READYSTATE_LOADING;

// set the alignment to the default of left
m_lAlignment = EALIGN_LEFT;

// don't forget this - DoPropExchange won't work without it
oMyDataPath.SetControl(this);
}

Next add the oMyDataPath property persistence to the DoPropExchange function (see Listing 7.6). Remember that without the call to SetControl in the constructor, this code will not function correctly.

Listing 7.6 MFCCONTROLWINCTRL.CPP--oMyDataPath Added to DoPropExchange

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(""));
PX_DataPath(pPX, _T("TextData"), oMyDataPath);
}

// 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(""));
PX_DataPath(pPX, _T("TextData"), oMyDataPath);
}
}

Finally you add the code to the GetTextData and SetTextData methods (see Listing 7.7). The GetTextData function simply returns a UNICODE version of the property value. The SetTextData implementation calls the Load function to load the data pointed to by the lpszNewValue parameter. After the Load function returns, the SetModifiedFlag and InvalidateControl functions are called to update the control and the data with the new information.

Listing 7.7 MFCCONTROLWINCTL.H--Get/Set TextData Property Implementation

BSTR CMFCControlWinCtrl::GetTextData()
{
// retrieve the path and allocate a BSTR from it
return (oMyDataPath.GetPath()).AllocSysString();
}

void CMFCControlWinCtrl::SetTextData(LPCTSTR lpszNewValue)
{
// load the DataPath variable based on the new information
this->Load(lpszNewValue, oMyDataPath);

// update the property
this->SetModifiedFlag();

// redraw the control
this->InvalidateControl();
}

Again, it is very important to understand that asynchronous properties are not a guarantee of improved performance in a general sense. They are merely an option available to you for creating Internet controls that are more responsive across slower network connections.

Static and Dynamic Property Enumeration

Property enumeration is a way of restricting a property to a specific set of valid values. An example of an enumeration is a property for determining the alignment of a control's displayed text: left-justified, centered, and right-justified, in your case. Another case is a property used to select the different languages a control supports. This is a good candidate for both a static set, say English and German, and a dynamic set, say for all the languages on a particular machine.

Adding property enumeration greatly enhances the look and feel of your control. This addition gives the user all the options that are possible, with a simple click of a mouse. As a result the user doesn't have to rather than trying to search through documentation or, worse yet, trying to guess the acceptable values.

When editing the value of an enumerated property within a property browser, note that development tools such as Visual Basic display all the values of the enumeration using a string representation rather than just the actual value that the control can accept. For a Boolean data type, the strings used are TRUE and FALSE, representing -1 or 0, respectively.

Two approaches can be taken when creating an enumeration for a property: You can use a static approach, with an enumeration defined in the control's ODL file, or a dynamic approach, with enumeration code implemented in the control itself.

Static Property Enumeration
ODL allows for the creation of C/C++ style enumeration declarations that conform to the same rules as C/C++. Like C/C++, the new enumeration can be used as data type within the ODL file. While this style of enumeration is by far the easiest, it is also the most restrictive because the enumeration is static to the type library. The Alignment property is a good candidate for an enumeration because internally that is how it is validated and used. Listing 7.8 contains the code that was added to your ODL file to support the Alignment enumeration.

Remember to generate a new UUID for the enumeration with the GUIDGEN.EXE application included with VC++.

The helpstring is what the user will see within the property browser of your development environment. If you leave off the helpstring, the actual numeric value will appear instead.

The last thing you did was to change the data type of the Alignment property from long to EALIGNMENT. This is required if the property is to display the enumerated values within the property browser.

NOTE: ODL is very flexible in that it allows much the same style of data type declaration and use as that of C or C++. Any data type that can be declared in ODL can also be referenced and used within the same and other ODL files, including interface declarations. In addition, other type libraries can be imported into an ODL file to provide access to other user-defined data types. In the case of the sample control, two types of libraries, STDOLE_TLB and STDTYPE_TLB, are included for the standard OLE interface and data type declarations.

The ODL documentation can be a little difficult to decipher, but we recommend that you at least review the documentation. It is well worth the effort just to see what you can and cannot do with your type library and how it will affect the container of your control or component.

It is absolutely critical that the developer of containers adhere to the standards established in the ODL documentation. You will find that some of the aspects of type library creation and use are based on cooperation and trust and that component developers depend on that.

Listing 7.8 MFCCONTROL.ODL--EALIGNMENT Enumeration Added to the MFCControl.odl File

typedef
[ uuid(7F369B90-380D-11d0-BCB6-0020AFD6738C) ]
enum tagAlignmentEnum
{
[helpstring("Left Justify")] EALIGN_LEFT = 0,
[helpstring("Right Justify")] EALIGN_RIGHT = 1,
[helpstring("Center")] EALIGN_CENTER = 2,
}EALIGNMENT;

// Primary dispatch interface for CMFCControlWinCtrl

[ uuid(14DD5C04-60DE-11D0-BEE9-00400538977D),
helpstring("Dispatch interface for MFCControlWin Control"), hidden ]
dispinterface _DMFCControlWin
{
properties:
// NOTE - ClassWizard will maintain property information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_PROP(CMFCControlWinCtrl)
[id(DISPID_READYSTATE), readonly] long ReadyState;
[id(1)] EALIGNMENT Alignment;
[id(DISPID_BACKCOLOR), bindable, requestedit] OLE_COLOR BackColor;
[id(2)] BSTR TextData;
//}}AFX_ODL_PROP
methods:
. . .

Dynamic Property Enumeration Dynamic property enumeration is the most flexible of the two types of property enumeration. It requires only a little more work on the part of the developer, and is worth the effort. Dynamic enumeration is perfect for situations where you need to restrict the data that can be entered into a property but are unable to determine until runtime what the valid values are. For example, a LocaleID property can enumerate all of the available LocaleIDs on the machine that the application is running on. Another property could enumerate the key field of a SQL database table. The possibilities are limitless and will provide your control a look and feel that goes far beyond the effort that was required to enable it. Dynamic property enumeration using MFC requires the implementation of three methods within your control: OnGetPredefinedStrings, OnGetPredefinedValue, and OnGetDisplayString, all of which are virtual functions declared in the COleControl class. Listing 7.9 shows the function prototypes that need to be added to the control's class declaration.

Listing 7.9 MFCCONTROLWINCTL.H--Dynamic Property Enumeration Function Prototypes Added to the CMFCControlWinCtrl Class

class CMFCControlWinCtrl : public COleControl
{
DECLARE_DYNCREATE(CMFCControlWinCtrl)

// Constructor
public:
CMFCControlWinCtrl();

BOOL OnGetPredefinedStrings(DISPID dispid, CStringArray* pStringArray, CDWordArray* pCookieArray);
BOOL OnGetPredefinedValue(DISPID dispid, DWORD dwCookie, VARIANT FAR* lpvarOut);
BOOL OnGetDisplayString( DISPID dispid, CString& strValue );

// Overrides
. . .

OnGetPredefinedStrings is called by the container application when it needs to load the strings and cookies for the enumeration (see Listing 7.10). The string is used as the text representation of the enumerated value to make it more meaningful to the user. The cookie is a 32-bit value that can be used in any way that is appropriate to identify the value associated with the display string of the enumeration. In your case, you are going to store the actual value that the string represents, but it could just as easily have been a pointer or index into some form of storage.

Listing 7.10 MFCCONTROLWINCTL.CPP--OnGetPredefinedStrings Implementation

BOOL CMFCControlWinCtrl::OnGetPredefinedStrings(DISPID dispid, CStringArray* pStringArray, CDWordArray* pCookieArray)
{
BOOL bResult = FALSE;

// which property is it
switch(dispid)
{
case dispidAlignment:
{
// add the string to the array
pStringArray->Add(_T("Left Justify"));
// add the value to the array
pCookieArray->Add(EALIGN_LEFT);

// add the string to the array
pStringArray->Add(_T("Center"));
// add the value to the array
pCookieArray->Add(EALIGN_CENTER);

// add the string to the array
pStringArray->Add(_T("Right Justify"));
// add the value to the array
pCookieArray->Add(EALIGN_RIGHT);

// set the return value
bResult = TRUE;
}
break;
}

return bResult;
}

The OnGetPredefinedStrings function is called for every one of the properties in your control, so your code will have to check the dispid that is passed to the function to make sure that the correct enumeration is going to the correct property. The dispids of the control properties can be found in the class declaration of your control and are maintained automatically by the ClassWizard (see Listing 7.11).

Listing 7.11 MFCCONTROLWINCTL.H--Control dispid Enumeration

. . .
// Dispatch and event IDs
public:
enum {
//{{AFX_DISP_ID(CMFCControlWinCtrl)
dispidAlignment = 1L,
dispidTextData = 2L,
dispidCaptionMethod = 3L,
dispidCaptionProp = 4L,
eventidChange = 1L,
//}}AFX_DISP_ID
};

. . .

When the user selects one of the enumerated values in the property browser, the control's OnGetPredefinedValue function is fired (see Listing 7.12). This allows the container to retrieve the actual value that should be stored in the property in place of the string representation. The function is passed the dispid identifying the property that is being changed and the cookie value that was assigned to the string representation in the OnGetPredefinedStrings function.

Listing 7.12 MFCCONTROLWINCTL.CPP-- OnGetPredefinedValue Implementation

BOOL CMFCControlWinCtrl::OnGetPredefinedValue(DISPID dispid, DWORD dwCookie, VARIANT FAR* lpvarOut)
{
BOOL bResult = FALSE;

// which property is it
switch(dispid)
{
case dispidAlignment:
// clear the variant
::VariantInit(lpvarOut);
// set the type to a long
lpvarOut->vt = VT_I4;
// set the value to the value that was stored with the string
lpvarOut->lVal = dwCookie;
// set the return value
bResult = TRUE;
break;
}

return bResult;
}

OnGetDisplayString (see Listing 7.13) is the last method to be called and is required if the programmer wants the text representation to appear next to the property in the property browser when the property is not being edited. The function is passed a dispid, again reflecting the current property, and a CString object reference. The control should place the string, as reflected by the current state of the property, into the CString object and exit the function.

Listing 7.13 MFCCONTROLWINCTL.CPP--OnGetDisplayString Implementation

BOOL CMFCControlWinCtrl::OnGetDisplayString( DISPID dispid, CString& strValue )
{
BOOL bResult = FALSE;

// which property is it
switch(dispid)
{
case dispidAlignment:
{
switch(m_lAlignment)
{
case EALIGN_LEFT:
strValue = _T("Left Justify");
break;
case EALIGN_CENTER:
strValue = _T("Center");
break;
case EALIGN_RIGHT:
strValue = _T("Right Justify");
break;
}

// set the return value
bResult = TRUE;
}
break;
}

return bResult;
}

Drawing the Control

Optimized drawing allows you to create drawing objects, such as pens or brushes, and rather than removing them when you are finished drawing, you can store them in your control member variables and use them again the next time your control draws itself. The benefit is that you create a pen once for the drawing lifetime of your control, instead of every time it draws. One thing to remember, though: Optimized drawing does not guarantee performance improvements. It simply supplies a framework for how drawing should be performed and how drawing resources should be used. A poorly written control is still poorly written, no matter how you slice it.

Standard and optimized drawings have a single tradeoff, and that is size versus speed. Standard drawing does not require member variables for the drawing objects that are created and used--thus requiring less instance data but more processing time--whereas optimized code will be the opposite.

An additional drawback to optimized drawing is that a container may not support it. A control must, at the very least, support standard drawing functionality, deferring to optimized only if appropriate.

For MFC, the scope of optimized drawing is very narrow compared to the OC 96 specification, but it will, nonetheless, result in performance improvements if taken advantage of. The OC 96 specification further breaks optimized drawing into what is known as aspects, but MFC is not designed to allow that kind of drawing. For more information on aspect drawing, please see the OLE Control 96 Specification that ships with the ActiveX SDK.

Optimized Drawing
In Chapter 6, you learn how to implement standard drawing. In this chapter, you will enhance the original implementation to take advantage of drawing optimization. Listing 7.14 reflects the change that is made to the original drawing code to use optimization. That's it. You just check to see whether the container supports optimized drawing. If the container supports optimized drawing, you leave the CBrush object alone and use it again when you have to draw the control's UI. Thus, you save time every time you redraw the UI because you load the brush only once. When the control is destroyed, the CBrush object will go out of scope and will remove the brush object for you. Optimized drawing is that simple, but it depends on the container application for support.

Listing 7.14 MFCCONTROWINCTL.CPP--Optimized OnDraw Function

. . .

// set the old font back
pdc->SelectObject(pOldFont);
// **
// ****** Get the text font ******

// The container does not support optimized drawing.
if(!IsOptimizedDraw())
{
// select the old brush back
pdc->SelectObject(pOldBrush);
}
}

Adding Clipboard and Drag and Drop Support

The Clipboard is an area of the Windows operating system that acts like a bulletin board for data that can be shared between applications. By means of a series of keystrokes or menu options, a user can copy data to the Clipboard from one application and paste the data from the Clipboard into another application.

Drag and Drop provides the user with the ability to transfer data between two applications by means of a mouse only. The user selects the data to transfer, holds down a mouse button on the selected data, drags the data to the location where is it to be added, and releases the mouse button, thus dropping the data into the new location. Drag and Drop support is similar in its implementation to Clipboard support, and you will take full advantage of it in your implementa

Adding Clipboard and Drag and Drop support to a control can make even the simplest implementations appear more professional and well-rounded.

Clipboard Support

The first step is deciding which keystrokes will be used to initiate the cut, copy, or paste operation. Fortunately, the Windows operating system already has a number of standards in this area. You will use Ctrl+X or Shift+Delete for Cut, Ctrl+C or Ctrl+Insert for Copy, and Ctrl+V or Shift+Insert for Paste.

Open the ClassWizard and select the Message Maps tab for the CMFCControlWinCtrl class. Double-click WM_KEYDOWN in the list of messages to add the OnKeyDown function to your message map (see fig. 7.2). The OnKeyDown function is where you are going to add your code to look for the keystroke combinations that will initiate the Clipboard transfers. Double-click the OnKeyDown member function to close the ClassWizard and open the source file for editing.

FIG. 7.2
Add the OnKeyDown message map with the ClassWizard.

Listing 7.15 shows the code that is added to OnKeyDown to support the keystroke combinations listed in the preceding paragraph. The implementation is simple; based on the particular state of the Ctrl or Shift keys and the correct keystroke, the function either copies the data to or copies the data from the Clipboard.

Listing 7.15 MFCCONTROLWINCTL.CPP--OnKeyDown Implementation

void CMFCControlWinCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
BOOL bHandled = FALSE;

// find out if the shift key is being held down
short sShift = ::GetKeyState(VK_SHIFT);
// find out if the control key is being held down
short sControl = ::GetKeyState(VK_CONTROL);

switch(nChar)
{
case 0x56: // `V' // PASTE
case 0x76: // `v'
// if the control key is being held down
if(sControl & 0x8000)
{
// get any text from the clipboard
this->GetDataFromClipboard();
// force the control to redraw itself
this->InvalidateControl(NULL);
// we don't need to pass this key to the base implementation
bHandled = TRUE;
}
case 0x43: // `C' // COPY or PASTE
case 0x63: // `c'
case VK_INSERT:
// if the control key is being held down
if(sControl & 0x8000)
{
// copy the data to the clipboard
this->CopyDataToClipboard();

// we don't need to pass this key to the base implementation
bHandled = TRUE;
}
// if the shift key is being held down it is a PASTE
else if(sShift & 0x8000 && nChar == VK_INSERT)
{
// get any text from the clipboard
this->GetDataFromClipboard();

// force the control to redraw itself
this->InvalidateControl(NULL);

// we don't need to pass this key to the base implementation
bHandled = TRUE;
}

break;
case 0x58: // `X' // CUT
case 0x78: // `x'
case VK_DELETE:
// if this is a shift delete OR CTRL-X/x
if((nChar == VK_DELETE && (sShift & 0x8000)) || ((nChar == 0x58 || nChar == 0x78) && (sControl & 0x8000)))
{
this->CopyDataToClipboard();

// clear the string since this is a CUT operation
m_cstrCaption.Empty();

// fire the global change event
this->FireChange();

// force the control to repaint itself
this->InvalidateControl(NULL);

// we don't need to pass this key to the base implementation
bHandled = TRUE;
}
break;
}

// if we didn't handle the character
if(!bHandled)
{
// and the control key is not being held down
if(!(sControl & 0x8000))
// send to the default handler
COleControl::OnKeyDown(nChar, nRepCnt, nFlags);
}
}

In addition to the code that you added to trap the keystrokes, you also need to add four methods, which you will examine in detail in the next section, for dealing with the Clipboard transfers.

CopyDataToClipboard will, as the name implies, get the data from the control, and using the helper function, PrepareDataForTransfer, will package the data and put it on the Clipboard.

GetDataFromClipboard will open the Clipboard and look for data formats that the control understands. Upon finding a suitable format, GetDataFromClipboard will use the helper function GetDataFromTransfer to store the data in the control.

The fact that the data transfer functions have been separated into two separate methods for each type of transfer, to and from the Clipboard, and then further broken down in each type of transfer into two separate steps will aid you when you enable the control for Drag and Drop support. This is because the basic data transfer mechanism is the same between the Clipboard and Drag and Drop and will allow you to rely on a large portion of shared code for each implementation.

Using Built-In Clipboard Formats
The Windows operating system defines a number of built-in data transfer formats for use with the Clipboard. You see the formats in the following list:

CF_TEXT
CF_BITMAP
CF_METAFILEPICT
CF_SYLK CF_DIF
CF_TIFF CF_OEMTEXT
CF_DIB CF_PALETTE
CF_PENDATA
CF_RIFF
CF_WAVE
CF_UNICODETEXT
CF_ENHMETAFILE
CF_HDROP
CF_LOCALE
CF_MAX
CF_OWNERDISPLAY
CF_DSPTEXT
CF_DSPBITMAP
CF_DSPMETAFILEPICT
CF_DSPENHMETAFILE
CF_GDIOBJFIRST
CF_GDIOBJLAST

The first implementation of Clipboard data transfer will rely on the CF_TEXT format. This is a general format for transferring non-UNICODE text data. There are two aspects to using the Clipboard: being a Clipboard source and being a Clipboard target. Being a Clipboard source refers to an application's capability to copy data to the Clipboard. Being a Clipboard target refers to an application's capability to copy data from the Clipboard. You will first learn how to enable a control as a Clipboard source and then how to enable a control as a Clipboard target.

Enabling a Control as a Clipboard Source
A Clipboard source is an application that puts data on the Clipboard for other applications to copy. An application must support two COM interfaces, IDataObject and IEnumFORMATETC, in order to qualify as a valid Clipboard source. MFC provides the classes COleDataSource and COleDataObject, which perform all of the work of implementing the interfaces for you. The OnKeyDown implementation takes advantage of two helper functions, CopyDataToClipboard and PrepareDataForTransfer, when copying data to the Clipboard. First you need to add the two function prototypes to the CMFCControlWinCtrl class (see Listing 7.16).

Listing 7.16 MFCCONTROLWINCTL.H--Clipboard Source Support Helper Function Prototypes

. . .
void CopyDataToClipboard(void);
void PrepareDataForTransfer(COleDataSource * opOleDataSource);
};
. . .

CopyDataToClipboard uses the COleDataSource class provided by MFC to connect to the Clipboard (see Listing 7.17). By calling the GetClipboardOwner function, your implementation first checks to see whether you already have an object on the Clipboard. GetClipboardOwner returns a pointer to a COleDataSource object if the Clipboard contains a COleDataSource object that you had previously set to the Clipboard using the SetClipboard function. If you are not the owner of the Clipboard, the method returns NULL. If you didn't get a reference to a COleDataSource object, you need to create one. Next you call the general method PrepareDataForTransfer passing in your COleDataSource object, which will copy the data from the control to the COleDataSource object. The final thing you do is put the COleDataSource object on the Clipboard, but only if you weren't the owner of the Clipboard at the time the transfer occurred. Setting the Clipboard again with the same object will result in an error.

Listing 7.17 MFCCONTROLWINCTL.CPP--CopyDataToClipboard Implementation

void CMFCControlWinCtrl::CopyDataToClipboard(void)
{
// get the clipboard if we are the owner
COleDataSource * opOleDataSource = COleDataSource::GetClipboardOwner();
BOOL bSetClipboard = FALSE;
// if we didn't get back a pointer
if(opOleDataSource == NULL)
{
// if this is a new clipboard object
bSetClipboard = TRUE;
// get a new data source object
opOleDataSource = new COleDataSource;
}
// call the common data preparation function
this->PrepareDataForTransfer(opOleDataSource);
// did we get a new clipboard object?
if(bSetClipboard)
// pass the data to the clipboard
opOleDataSource->SetClipboard();
}

PrepareDataForTransfer is used to create the necessary memory structures and to prepare the COleDataSource object that will be used in the data transfer (see Listing 7.18). For your data transfer, you are going to place the Caption on the Clipboard using the Clipboard format CF_TEXT. In order to put the text data on the Clipboard, you have to create a global memory object and copy the data to it. After you create the global object, you store it in the COleDataSource object using the CacheGlobalData function. Don't worry about cleaning up any data that was previously set in the COleDataSource object; the CacheGlobalData function will do that for you.

Listing 7.18 MFCCONTROLWINCTL.CPP-- PrepareDataForTransfer Implementation

void CMFCControlWinCtrl::PrepareDataForTransfer(COleDataSource *
opOleDataSource)
{
// get the length of the data to copy
long lLength = m_cstrCaption.GetLength() + 1;
// create a global memory object
HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(TCHAR)
* lLength);

// lock the memory down
LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(hGlobal);
// copy the string
for(long lCount = 0; lCount < lLength - 1; lCount++)
lpTempBuffer[lCount] = m_cstrCaption.GetAt(lCount);
// null terminate the string
lpTempBuffer[lCount] = `\0';
// unlock the memory
::GlobalUnlock(hGlobal);
// cache the data
opOleDataSource->CacheGlobalData(CF_TEXT, hGlobal);
}


Enabling a Control as a Clipboard Target The opposite of being a Clipboard source is being a Clipboard target. First you need to update the CMFCControlWinCtrl class to include two new helper functions (see Listing 7.19).

Listing 7.19 MFCCONTROLWINCTL.H--Clipboard Target Support Helper Function Prototypes

. . .
void GetDataFromClipboard(void);
. . .
BOOL GetDataFromTransfer(COleDataObject * opOleDataObject);
};
. . .

Getting data from the Clipboard is almost as simple as it is to put the data on the Clipboard in the first place. GetDataFromClipboard uses the COleDataObject MFC class to attach to the Clipboard and, if successful, passes the object to the GetDataFromTransfer helper function (see Listing 7.20).

Listing 7.20 MFCCONTROLWINCTL.CPP--GetDataFromClipboard Implementation

void CMFCControlWinCtrl::GetDataFromClipboard(void)
{
// get a data object
COleDataObject oOleDataObject;
// attach it to the clipboard
if(!oOleDataObject.AttachClipboard())
return;
// transfer the data to the control
this->GetDataFromTransfer(&oOleDataObject);
}

GetDataFromTansfer enumerates all of the available formats in the COleDataObject object using BeginEnumFormats (see Listing 7.21). To optimize the search a little bit, you should first see whether the Clipboard even contains any data that you can use. In this case, you are looking for the CF_TEXT format. The implementation contains a simple while loop that will execute as many times as there are formats in the COleDataObject object. When you locate a CF_TEXT format, you retrieve its global memory object with the function GetGlobalData and copy the data that it points to into the control. Do not destroy the data that was retrieved with this method--you are not its owner; the COleDataObject is.

Listing 7.21 MFCCONTROLWINCTL.CPP--GetDataFromTransfer Implementation

BOOL CMFCControlWinCtrl::GetDataFromTransfer(COleDataObject * opOleDataObject)
{
BOOL bReturn = FALSE;
// prepare for an enumeration of the clipboard formats available
opOleDataObject->BeginEnumFormats();
// is there a text format available
if(opOleDataObject->IsDataAvailable(CF_TEXT))
{
FORMATETC etc;
// while there are formats to enumerate
while(opOleDataObject->GetNextFormat(&etc))
{
// is this a format that we are looking for?
if(etc.cfFormat == CF_TEXT)
{
// get the global data for this format
HGLOBAL hGlobal = opOleDataObject->GetGlobalData (etc.cfFormat, &etc);
// lock down the memory
LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock (hGlobal);
// store the data
m_cstrCaption = lpTempBuffer;
// call the global routine
this->FireChange();
// unlock the memory
::GlobalUnlock(hGlobal);
// return success
bReturn = TRUE;
}
}
}
// if we found a format
if(bReturn == TRUE)
// force the control to repaint itself
this->InvalidateControl(NULL);
// return the result
return bReturn;
}

Custom Clipboard Formats The Clipboard is able to support custom formats, as well as built-in formats, defined by the operating system. Fortunately, the implementation of custom formats for the Clipboard is the same as for Drag and Drop. First you will learn how to support Drag and Drop and then how to support custom formats.

Drag and Drop Support

The fundamentals of Drag and Drop support are very similar to Clipboard support and rely on the same MFC classes, COleDataSource and COleDataObject, for their implementation. Since you have broken your code into separate functions--that is, the Clipboard specific code is isolated from the basic data transfer functions--most of your implementation is complete.

Using Built-In Drag and Drop Formats
Since Drag and Drop is essentially a Clipboard transfer, with fewer steps involved, Drag and Drop uses the same built-in data formats as the Clipboard transfers do. See the list of supported formats earlier in this chapter. As with Clipboard transfers, there are two sides to the Drag and Drop coin. An application can be a Drag and Drop source or a Drag and Drop target, or both.

Enabling a Control as a Drag and Drop Source To qualify as a Drag and Drop source, your only implementation requirements are that you create a COleDataSource object, call the method DoDragDrop, and have some way of initiating the Drag and Drop operation in the first place. In addition to the IDataObject and IEnumFORMATETC interfaces, the COleDataSource defines the IDropSource interface, all of which are necessary for Drag and Drop source support. The first step is to initiate a Drag and Drop operation. You are going to use the left mouse button down event, provided by MFC, to initiate the Drag and Drop operation. Open the ClassWizard, and select the Message Maps tab. Select the class CMFCControlWinCtrl, and double-click the WM_LBUTTONDOWN message to add the OnLButtonDown function (see fig. 7.3).

To open the source file and add your code, double-click the OnLButtonDown function in the Member functions list box.

FIG. 7.3
Add the WM_LBUTTONDOWN message map.

The OnLButtonDown implementation is similar to the Clipboard method CopyDataToClipboard (see Listing 7.22). Again, you are using a COleDataSource object and loading it with your data with the PrepareDataForTransfer call.

To actually initiate the Drag and Drop operation, you call DoDragDrop specifying the constant DROPEFFECT_COPY, which indicates that this is a copy operation. The constant DROPEFFECT_COPY is used for two purposes. The first, and most obvious to the user, is that the mouse cursor will change to indicate that a copy drag is in progress. The second is to inform the drop target as to the intent of the drop operation.

Listing 7.22 MFCCONTROLWINCTL.CPP--OnLButtonDown Implementation

void CMFCControlWinCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
COleDataSource oOleDataSource;
// call the common data preparation function
this->PrepareDataForTransfer(&oOleDataSource);
// start the Drag and Drop operation
oOleDataSource.DoDragDrop(DROPEFFECT_COPY);
// call the base class implementation
COleControl::OnLButtonDown(nFlags, point);
}

Now that the control has been enabled as a Drag and Drop source, it only makes sense to enable it as a Drag and Drop target.

Enabling a Control as a Drop Target
To qualify as a Drag and Drop target, a control must register itself as a Drag and Drop target and must implement the IDropTarget interface. The MFC class COleDropTarget provides the needed interface definition. However, it is not enough to rely on the default implementation of the COleDropTarget class to enable the control as a Drag and Drop target. You are required to inherit your own specialized version of the class so that you can override some of its functions. Call the new class CMyOleDropTarget, and add it to the CMFCControlWinCtl class (see Listing 7.23). You need to overload the methods OnDragOver and OnDrop for your specific implemen- tation as a drop target. Also added is a member variable of type CMyOleDropTarget called oMyOleDropTarget.

The class CMyOleDropTarget needs to be a friend of the CMFCControlWinCtrl class so that the CMyOleDropTarget class may call the general GetDataFromTransfer function to store the data in the control.

Listing 7.23 MFCCONTROLWINCTL.H--CMyOleDropTarget Class Declaration Added to the CMFCControlWinCtl Class

class CMyOleDropTarget: public COleDropTarget
{
public:
CMFCControlWinCtrl * opMFCControlWinCtrl;
DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
} oMyOleDropTarget;
friend CMyOleDropTarget;

As we stated earlier, an application must register itself with the operating system in order to be a valid Drag and Drop target. Register the control as a Drag and Drop target using the function, Register, in your OnCreate method for the control. To add the OnCreate function to the class, you use the ClassWizard. Open the ClassWizard, and select the Message Maps tab. Select the class CMFCControlWinCtrl, and double-click the WM_CREATE message to add the OnCreate function to your class (see fig. 7.4).

FIG. 7.4
Add the WM_CREATE message map.

To add your code, open the source file by double-clicking the OnCreate function in the Member functions list box.

Register is the only call that is required to enable your control as a drop target (see Listing 7.24). The member variable opMFCControlWinCtrl is set with a reference to the control so you can call the general method GetDataFromTransfer to put the data into your control; this is specific to your implementation and not a requirement for Drag and Drop target support.

Listing 7.24 MFCCONTROLWINCTL.CPP--OnCreate Implementation

int CMFCControlWinCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;

// let's register ourselves as drop targets
oMyOleDropTarget.Register(this);
// store a reference to the control so we can communicate back to the control
oMyOleDropTarget.opMFCControlWinCtrl = this;

return 0;
}

The OnDragOver function is used to instruct Windows that your control is a valid or invalid drop target for the current drag operation (see Listing 7.25). The implementation of OnDragOver first looks to see whether the mouse button is being held down. If it is, OnDragOver then looks for the availability of the CF_TEXT format. If it finds the CF_TEXT format, the OnDragOver function returns the constant DROPEFFECT_COPY, indicating to Windows that the control is a valid drop target for the particular drop operation that is currently in effect. Otherwise, OnDragOver returns DROPEFFECT_NONE, indicating that a drop should not be allowed.

Listing 7.25 MFCCONTROLWINCTL.CPP--OnDragOver Implementation

DROPEFFECT CMFCControlWinCtrl::CMyOleDropTarget::OnDragOver(CWnd* pWnd,
COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
// if the left mouse button is being held down
if(dwKeyState | MK_LBUTTON)
{
// is there a text format available
if(pDataObject->IsDataAvailable(CF_TEXT))
return DROPEFFECT_COPY;
// everything else we can't deal with
else
return DROPEFFECT_NONE;
}
else
// not the left mouse
return DROPEFFECT_NONE;
}

OnDrop is where you call GetDataFromTransfer, the same function that you created for your Clipboard operations, to copy the data from the drop source into your control (see Listing 7.26).

Listing 7.26 MFCCONTROLWINCTL.CPP--OnDrop Implementation

BOOL CMFCControlWinCtrl::CMyOleDropTarget::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
// transfer the data to the control
return opMFCControlWinCtrl->GetDataFromTransfer(pDataObject);
}

Custom Drag and Drop Formats Like the Clipboard, Drag and Drop can also support custom data transfer formats. The next section will examine in detail how to modify your existing code to support custom data formats for Clipboard and Drag and Drop operations, both with only a single code change to the source and target data transfer operations.

Custom Clipboard and Drag and Drop Formats

Custom Clipboard and Drag and Drop formats are a way for applications to trade information on a more intimate basis. The data transferred can be of any type and structure that the applications can create and use. When using custom Clipboard formats, all applications that will use the format must first call RegisterClipboardFormat passing in the name of the format.

Before you add the code to register your custom format, add a member variable, m_uiCustomFormat, to your class definition (see Listing 7.27). You will use the value that this member variable holds later in your implementation to determine whether a valid format has been registered.

Listing 7.27 MFCCONTROLWINCTL.H--m_uiCustomFormat Member Variable Added to the CMFCControlWinCtrl Class

. . .
// custom format storage variable
UINT m_uiCustomFormat;
};

Now add the custom format registration code to the constructor of the class CMFCControlWinCtrl (see Listing 7.28).

The RegisterClipboardFormat function, if it succeeds, will return the ID of the newly registered or already existing (in the case where the format is already registered) custom Clipboard format. It is important that all applications that are going to use the custom format call this method.

Listing 7.28 MFCCONTROLWINCTL.CPP--Custom Clipboard Format Registered in the CMFCControlWinCtrl Constructor

CMFCControlWinCtrl::CMFCControlWinCtrl()
{
InitializeIIDs(&IID_DMFCControlWin, &IID_DMFCControlWinEvents);
// TODO: Call InternalSetReadyState when the readystate changes.
m_lReadyState = READYSTATE_LOADING;
// set the alignment to the default of left
m_lAlignment = EALIGN_LEFT;
// don't forget this - DoPropExchange won't work without it
oMyDataPath.SetControl(this);
// register a custom clipboard format
m_uiCustomFormat =
::RegisterClipboardFormat(_T("MFCControlWinCustomFormat"));
}

After the custom format has been registered, it is a simple matter to add the code to your implementation that stores or retrieves the data from the COleDataSource and COleDataObject objects. Using the code that was created for your standard data transfers as a base, you will now add the custom data transfer code.

PrepareDataForTransfer (see Listing 7.29) differs only slightly from its original implementation. For the custom data transfer, you are going to send the m_lAlignment value in addition to the caption. You can use any data type or structure, including user-defined data types, for your custom format. The only requirement is that both the source and the target applications understand the format being transferred.

Listing 7.29 MFCCONTROLWINCTL.CPP--PrepareDataForTransfer Function Updated to Support Custom Clipboard Formats

void CMFCControlWinCtrl::PrepareDataForTransfer(COleDataSource *
opOleDataSource)
{
// get the length of the data to copy
long lLength = m_cstrCaption.GetLength() + 1;
// create a global memory object
HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(TCHAR) * lLength);

// lock the memory down
LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(hGlobal);
// copy the string
for(long lCount = 0; lCount < lLength - 1; lCount++)
lpTempBuffer[lCount] = m_cstrCaption.GetAt(lCount);
// null terminate the string
lpTempBuffer[lCount] = `\0';
// unlock the memory
::GlobalUnlock(hGlobal);
// cache the data
opOleDataSource->CacheGlobalData(CF_TEXT, hGlobal);
// if we have custom clipboard format support
if(m_uiCustomFormat)
{
// create a global memory object
HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(m_lAlignment));

// lock the memory down
LONG * lpTempBuffer = (LONG *) ::GlobalLock(hGlobal);
// set our data buffer
*lpTempBuffer = m_lAlignment;
// unlock the memory
::GlobalUnlock(hGlobal);
// cache the data
opOleDataSource->CacheGlobalData(m_uiCustomFormat, hGlobal);
}
}

NOTE: Any number of data formats can be transferred at one time for a single data source object. It is up to the receiving application to deal with this possibility and retrieve the data correctly. The MFC implementation of the COleDataSource class does not allow duplicate formats to be available in the same object, but that's not to say that this can't happen. Non-MFC applications have complete freedom to implement data sources any way they see fit. Remember yours may not be the only data being transferred.



The GetDataFromTransfer implementation is almost identical to your original implementation (see Listing 7.30). The only difference is that the function now looks for the custom format as well as the CF_TEXT format.

Listing 7.30 MFCCONTROLWINCTL.CPP--GetDataFromTransfer Function Updated to Support Custom Clipboard Formats

BOOL CMFCControlWinCtrl::GetDataFromTransfer(COleDataObject * opOleDataObject)
{
BOOL bReturn = FALSE;
// prepare for an enumeration of the clipboard formats available
opOleDataObject->BeginEnumFormats();
// is there a text format available
// && there is no custom format
// || there is a custom format and the format is available
if(opOleDataObject->IsDataAvailable(CF_TEXT) && (!m_uiCustomFormat || (m_uiCustomFormat && opOleDataObject->IsDataAvailable (m_uiCustomFormat))))
{
FORMATETC etc;
// while there are formats to enumerate
while(opOleDataObject->GetNextFormat(&etc))
{
// is this a format that we are looking for?
if(etc.cfFormat == CF_TEXT)
{
// get the global data for this format
HGLOBAL hGlobal = opOleDataObject->GetGlobalData (etc.cfFormat, &etc);
// lock down the memory
LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(hGlobal);
// store the data
m_cstrCaption = lpTempBuffer;
// call the global routine
this->FireChange();
// unlock the memory
::GlobalUnlock(hGlobal);
// return success
bReturn = TRUE;
}
// if we have custom clipboard format support
else if(m_uiCustomFormat && etc.cfFormat == m_uiCustomFormat)
{
// get the global data for this format
HGLOBAL hGlobal = opOleDataObject->GetGlobalData (etc.cfFormat, &etc);

// lock the memory down
LONG * lpTempBuffer = (LONG *) ::GlobalLock(hGlobal);
// get the data from our data buffer
m_lAlignment = *lpTempBuffer;
// unlock the memory
::GlobalUnlock(hGlobal);
// return success
bReturn = TRUE;
}
}
}
// if we found a format
if(bReturn == TRUE)
// force the control to repaint itself
this->InvalidateControl(NULL);
// return the result
return bReturn;
}

Because both the Clipboard support and the Drag and Drop support rely on the same data transfer routines to exchange data, you have the added benefit of supporting the custom data formats for each, while having to maintain only two functions, instead of four.

Subclassing Existing Windows Controls

Since the early days of Windows programming, programmers have enjoyed the option of using the default behavior of existing Windows controls and extending them slightly to create new and more powerful controls. This technique of creating Windows controls is referred to as subclassing (and there is also superclassing, depending on the technique you use). The same is still true for ActiveX controls.

When you created the original application code with the MFC AppWizard, you created a total of three controls. One of those controls, CMFCControlSubWin, subclassed a Windows BUTTON control.

Listing 7.31 contains all of the additional code that is required of an ActiveX control to subclass a control, which, by the way, was generated for you automatically by the MFC AppWizard when you created the project.

Listing 7.31 MFCCONTROLSUBWINCTL.CPP--Additional Code Requirements for ActiveX Controls that Subclass Existing Windows Controls

/////////////////////////////////////////////////////////////////////////////

// CMFCControlSubWinCtrl::OnDraw - Drawing function
void CMFCControlSubWinCtrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
DoSuperclassPaint(pdc, rcBounds);
}
. . .
/////////////////////////////////////////////////////////////////////////////
// CMFCControlSubWinCtrl::PreCreateWindow - Modify parameters for CreateWindowEx
BOOL CMFCControlSubWinCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
cs.lpszClass = _T("BUTTON");
return COleControl::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CMFCControlSubWinCtrl::IsSubclassedControl - This is a subclassed control
BOOL CMFCControlSubWinCtrl::IsSubclassedControl()
{
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// CMFCControlSubWinCtrl::OnOcmCommand - Handle command messages
LRESULT CMFCControlSubWinCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
{
#ifdef _WIN32
WORD wNotifyCode = HIWORD(wParam);
#else
WORD wNotifyCode = HIWORD(lParam);
#endif
// TODO: Switch on wNotifyCode here.
return 0;
}

OnDraw is where you call DoSuperClassPaint to instruct the subclassed control to paint itself. Calling DoSuperClassPaint will work only for those controls that accept a device context handle (HDC) as the WPARAM of their WM_PAINT message.

PreCreateWindow is where the most important action of the whole subclassing process occurs; you identify, by name, the control to subclass. The name used can be any valid Windows control, including user-defined controls. As long as the control is a registered, valid Windows control, you can subclass it.

It's pretty obvious what IsSubclassedControl is used for. MFC queries the function to determine whether it subclasses a Windows control.

OnOcmCommand is where you get all of your reflected windows messages. Within this function, you can respond to messages like button clicks and edit change notifications.

Microsoft has created sample code and plenty of documentation regarding control subclassing, so we won't get into any implementation details here.

NOTE: Note that a subclassed control may not behave exactly as its original implementation does. In the case of subclassing a list box, the font that the control uses may not match that of the container. Getting the subclassed list box to use the correct font can be a little difficult and may require sending messages directly to the subclassed window rather than allowing the MFC base implementation to define the font.

Subclassing an existing control is a risky proposition at best. The implementation of the particular Windows control you are subclassing may make it difficult, if not impossible, to subclass from within the confines of the MFC control framework. The concept of multiple windows' handles within a single control is one area where subclassing using MFC will come up short. It may be very difficult to reliably retrieve the secondary window handles of the control if the original implementation does not provide access to its child windows.

Also, some of the ActiveX features detailed in this chapter may not be appropriate or possible because you are simply wrapping an existing window's control. Creating a windowless control is impossible because you are depending on the window's control for the implementation.

Dual-Interface Controls

At this time, none of the control containers can, or will, take advantage of dual-interfaces implemented in a control, but that is not to say that they won't in the future. The addition of dual-interface support to your control is exactly the same as adding dual-interface to ActiveX Automation Servers. It will add only a little more code to your application and should result in significant performance improvement for the control if and when containers are built to support dual-interface.

Microsoft strongly suggests that controls be created with dual-interface support, in spite of the fact that, at the time of the writing of this book, none of Microsoft's containers can use them.

Other ActiveX Features

Finally, the section that you all have been waiting for: Advanced ActiveX features. Don't stand up and jump just yet. Most of the features require little code, if any at all, and really aren't intended for anything more than trimming a few more precious milliseconds from your load times and runtimes.

When the AppWizard creates the basic source files for your control, it adds a member function GetControlFlags to your class header and source files. GetControlFlags is where the control informs MFC about the kind of ActiveX support it has by returning a set of flags. The documentation for GetControlFlags lists all of the valid flags that can be returned from this function, thereby indicating the control's level of ActiveX support.

You covered the features, optimized drawing and asynchronous properties, earlier in the chapter. Now take a look at the implementation specifics for the rest of the features.

Windowless Activation

Setting this flag will allow a control to be created without the burden of creating a window, significantly improving the control's start-up time.

The implementation of windowless controls versus windowed controls, for the most part, will remain unchanged since most of the windowless details are hidden from the control programmer.

For your own implementation of the CMFCControlWinCtrl class, nothing that you did in your implementation would prevent you from using this feature. The main restriction when writing a windowless control is that you cannot access the window handle directly without first checking to see whether it is valid. If your control implementation never uses the window handle, you should be fine.

To prove the point regarding the implementation of windowed and windowless controls, the CMFCControlNoWin class's implementation of OnDraw contains the same code as your windowed implementation.

One thing to note though is that the container is the deciding factor when it comes to windowed versus windowless support. A window will be created for the control automatically if the container does not have windowless support. This is a requirement for all controls, regardless of the tool used to create them.

See the VC++ books online article "OLE Controls: Optimization" for the specific implementation details regarding message maps and routing for windowless controls.

Flicker-Free Activation

Flicker-free activation simply says that the control will not receive a notification to draw its UI when it transitions between an active and inactive state, and vice versa. The only requirement of a control with this feature is that the control's UI remains the same regardless of the state of the control.

Unclipped Device Context

Unclipped device context is a commitment more than a feature. When this flag is set, the control is telling the container that it will not draw outside of its client area. Setting the unclipped device context flag will improve performance by eliminating unnecessary clipping tests. Unclipped device context cannot be used with the windowless controls.

Mouse Pointer Notifications When Inactive

Mouse pointer notifications when inactive allows the control to receive mouse notification messages, even though the window may not be active. Mouse pointer notifications when inactive is used for those users who turned off the feature "Activate when visible" when first creating their control project with the AppWizard.

The only special circumstance to be concerned with is the case of Drag and Drop operations, which require an activated window. The programmer of the control must override the control's GetActivationPolicy function to instruct the container how to activate the control when it becomes the target of a Drag and Drop operation.

See the VC++ books online article "OLE Controls: Optimization" for the specific implementation details regarding this feature.

From Here...

In this chapter, we demonstrated fairly simple techniques for creating unique and interesting controls and control features. We built upon simple concepts and methods that, in turn, resulted in a control that is far greater than the sum of its parts. A little bit of work and forethought can go a long way when using MFC.

MFC provides a large and robust framework for creating ActiveX controls. The ease of use and support provided by the AppWizard, ClassWizard, and the VC++ IDE make MFC unbeatable for rapid control development. Unfortunately, those very same features and functionality make MFC a hard choice when building small, fast controls for use over the Internet or in applications where performance is an issue. DLL load times and unnecessary code overhead can make MFC unreasonable to use for simple, lightweight controls. On the other hand, MFC hides a large number of the details surrounding control implementation and leaves you free to focus on the control and its specific implementation details. All of these things must be considered when choosing MFC as a control development framework.

The next four chapters examine in detail how to create a similar control implementation using ATL and BaseCtl.