Chapter 32

Advanced Programming with ATL

In preceding chapters, you learned how to use the Active Template Library (ATL) in your applications and ActiveX controls. Now it's time to explore some advanced features of ATL that you can use to extend and enhance your controls? usefulness. In addition to familiar features like Clipboard and Drag and Drop support, you will learn how to implement asynchronous properties and optimized drawing, which are key parts of the OLE Control 96 (OC 96) specification.



The complete source code and executable file for the ATLControlWin control is located in the Chap31\ATLControlWin folder on the book's CD-ROM.

Adding Asynchronous Properties

In Chapter 31, ?Creating ActiveX Controls with ATL,? you learned how to use ambient, stock, and user-defined properties in your ATL ActiveX controls. In addition to these three types, ATL enables you to use asynchronous properties in your controls.

Asynchronous properties are properties in which the data is loaded in a background process or thread. Properties that typically contain a large amount of data, such as bitmapped images or sounds, can be made asynchronous so as not to interfere with the normal processing of the control and the container. When an asynchronous property is loaded, the control can still respond to user interface events, and the user can stop the data loading.

For example, a control that downloads and parses a file of stock quotes from an Internet server can load the data asynchronously. When the control needs to load data, it can begin the download and register a callback function to be called when the transfer is complete. The callback function is called to signal that the property's data has been retrieved and that it can be used normally. Your controls can process the incoming data as it arrives, or they can wait until all data has arrived before doing anything with it.

These callbacks are implemented with OLE's IBindStatusCallback interface and the corresponding CBindStatusCallback class. In addition, your control should support the ReadyState property, which is used by containers to tell what state the control is in. To properly support ReadyState, you also need to add the ReadyStateChange event, which is used by the control to notify the container that the value of the control's ReadyState has changed.

Adding the ReadyState Property

You add the ReadyState property just like any other property, as described in Chapter 31:
  1. From the ClassView tab in the Project Workspace window, select the IATLControlWin interface.
  2. Click the right mouse button, and select the Add Property menu item.
  3. In the Add Property to Interface dialog box, set the Property Type to long and the Property Name to ReadyState.
  4. Uncheck the Put Function check box, and leave the remainder of the settings at their default values (see Figure 32.1). 
  5. Click OK to confirm the entry and close the dialog box.



Add the ReadyState property to the control.

FIG. 32.1


ReadyState is a stock property, even though it doesn't appear in the ATL Object Wizard stock property dialog box. Because you added it as a user-defined property, you must change its dispid. Before you proceed, open the ATLControl.idl file, and change the dispid of the ReadyState property to DISPID_READYSTATE. Listing 32.1 shows the IDL file after editing.


Listing 32.1 ATLCONTROL.IDL?Changing the dispid of the ReadyState Property to the Stock Property dispid?DISPID_READYSTATE

. . .

interface IATLControlWin : IDispatch

{

[id(1), helpstring("method CaptionMethod")] HRESULT CaptionMethod(

[in] BSTR bstrCaption, [in, optional] VARIANT varAlignment,

[out, retval] long * lRetVal);

[propget, id(DISPID_READYSTATE), helpstring("property ReadyState")]

HRESULT ReadyState([out, retval] long *pVal);

[propget, id(DISPID_BACKCOLOR), helpstring("property BackColor")]

HRESULT BackColor([out, retval] OLE_COLOR *pVal);

[propput, id(DISPID_BACKCOLOR), helpstring("property BackColor")]

HRESULT BackColor([in] OLE_COLOR newVal);

[propget, id(dispidCaptionProp), helpstring("property CaptionProp")]

HRESULT CaptionProp([in, optional] VARIANT varAlignment,

[out, retval] BSTR *pVal);

[propput, id(dispidCaptionProp), helpstring("property CaptionProp")]

HRESULT CaptionProp([in, optional] VARIANT varAlignment,

[in] BSTR newVal);

[propget, id(dispidAlignment), helpstring("property Alignment")]

HRESULT Alignment([out, retval] long *pVal);

[propput, id(dispidAlignment), helpstring("property Alignment")]

HRESULT Alignment([in] long newVal);

};

. . .


Of course, implementing the ReadyState property requires a member variable to store ReadyState's value. Add the m_lReadyState member to the class declaration of the CATLControlWin class, as shown in Listing 32.2.

Listing 32.2 ATLCONTROLWIN.H?Adding m_lReadyState to the CATLControlWin Class Definition

int iCharWidthArray[256];

int iCharacterSpacing, iCharacterHeight;

// for the ReadyState property

long m_lReadyState;

};


Don't forget to initialize the m_lReadyState member variable in the constructor of the CATLControlWin class; if you don't, the asynchronous transfers might not progress as you'd expect!

The last step is to implement the get_ReadyState function, which simply returns the current value of the m_lReadyState member variable, as shown in Listing 32.3.

Listing 32.3 ATLCONTROLWIN.CPP?Implementing the get_ReadyState Function so that It Returns the m_lReadyState Member

STDMETHODIMP CATLControlWin::get_ReadyState(long * pVal)

{

// set the return value to the value of the member variable

*pVal = m_lReadyState;

return S_OK;

}

Supporting the ReadyStateChange Event

Now that you've added the ReadyState property, you still need to provide a notification hook to signal when ReadyState changes. You can do this with the ReadyStateChange event. Open the ATLControl.idl file, and add the ReadyStateChange function to the event interface, as shown in Listing 32.5.


Listing 32.4 ATLCONTROLWIN.IDL?Adding the ReadyStateChange Event to the IDL File so that It Can Be Triggered

. . .

[

uuid(C31D4C71-7AD7-11d0-BEF6-00400538977D),

helpstring("ATLControlWin Event Interface")

]

dispinterface _DATLControlWin

{

properties:

methods:

[id(1)] void Change([in, out]BSTR * bstrCaption,

[in, out] long * lAlignment);

[id(DISPID_READYSTATECHANGE)] void ReadyStateChange();

};

. . .


Remember that ATL doesn't automatically support events? you must manually rebuild the CPATLControl.h file that contains your connection point interface definitions. In Chapter 31, you added support for the Change event. To add support for ReadyStateChange, you must rebuild the file that defines the connection point interfaces so that the connection points include both events. To do this, follow these steps:

  1. Compile the IDL file; you must do this first, because the event interface header file is built from the type library.
  2. From the Project menu, select the Add to Project menu item, and then select the Components and Controls submenu item.
  3. In the Components and Controls Gallery dialog box, double-click the Developer Studio Components folder, and then double-click the ATL Proxy Generator icon. Click OK in the confirmation dialog that asks whether you want to insert the Proxy Generator.
  4. When the ATL Proxy Generator dialog box appears, click the ... button to display the Open dialog box. Select the ATLControl.tlb file, and click Open.
  5. Select the DATLControlWin entry in the Not Selected list box, and click the > button to move the entry to the Selected list box. Ensure that the Proxy Type is set to Connection Point and click Insert.
  6. A Save dialog box will appear with the file CPATLControl.h in the File name edit box. Click Save to continue. Click Yes to replace the existing CPATLControl.h file.
  7. Click OK in the confirmation dialog box that indicates the operation was successful.
  8. Close the ATL Proxy Generator and Components and Controls Gallery dialog boxes.


The Fire_ReadyStateChange method has now been added to the CProxy_DATLControlWin class.

Asynchronous properties are based on URLs, not on the data type of the data to be downloaded. The URL associated with each asynchronous property should be stored in a string property of the control. For our control, the property is called TextDataPath, and you need to add it to the control. From the ClassView tab in the Project Workspace window, select the IATLControlWin interface, click the right mouse button, and select the Add Property... menu item.

In the Add Property to Interface dialog box, set the Property Type to BSTR, the Property Name to TextDataPath, and leave the remainder of the settings at their default values(see Figure 32.2). Click OK to confirm the entry and close the dialog.



Add the TextDataPath property to the control by using the ATL ClassWizard.
FIG. 32.2

Because you added a new property, you should also add a new dispid constant, dispidTextDataPath, to the PROPDISPIDS enumeration in the ATLControl.idl file, and then update the TextDataPath function to use the constant value (see Listing 32.5).

Listing 32.5 ATLCONTROL.IDL?Adding the dispidTextDataPath Enumeration to the IDL File

. . .

typedef enum propdispids

{

dispidAlignment = 2,

dispidCaptionProp = 3,

dispidTextDataPath = 4,

}PROPDISPIDS;

. . .

interface IATLControlWin : IDispatch

{

[id(1), helpstring("method CaptionMethod")] HRESULT CaptionMethod(

[in] BSTR bstrCaption, [in, optional] VARIANT varAlignment,

[out, retval] long * lRetVal);

[propget, id(dispidTextDataPath), helpstring("property TextDataPath")]

HRESULT TextDataPath([out, retval] BSTR *pVal);

[propput, id(dispidTextDataPath), helpstring("property TextDataPath")]

HRESULT TextDataPath([in] BSTR newVal);

. . .


The TextDataPath property is used to store the URL of the data that the property represents. To complete your implementation of the property, add a member variable named m_bstrTextDataPath to keep the URL available to the class. This member is declared to be of type CComBSTR, which is a BSTR wrapper class provided with ATL; see the ATL documentation for more information. We?ve chosen to use CComBSTR to demonstrate the different implementation styles available to you with ATL. Instead, you could use an LPTSTR or BSTR without difficulty.

For this example, we're assuming our URL will always return a block of text, so you need to add the m_bstrText member variable, also of type CComBSTR, to store the data as it is downloaded. You'll see these two members used in "Transferring the Data" later in the chapter; Listing 32.6 shows their declarations.


Listing 32.6 ATLCONTROLWIN.H?Adding Member Variables for the URL and the Data that It Returns

. . .

//OnData will be used as a callback functin by the CBindStatusCallback object.

//OnData will be called periodically with data from the asynchronous transfer

void OnData(CBindStatusCallback<CATLControlWin>* pbsc, BYTE* pBytes, DWORD dwSize);

protected:

. . .

// for the ReadyState property

long m_lReadyState;

// for the TextDataPath property

CComBSTR m_bstrTextDataPath;

// to hold the data as it is passed in

CComBSTR m_bstrText;

};

Transferring the Data

The get_TextDataPath and put_TextDataPath accessor functions are where the asynchronous data transfer actually takes place, as shown in Listing 32.7. All get_TextDataPath does is returns the current value stored in the m_bstrTextDataPath member variable. 

The put_TextDataPath function stores the requested location of the data and then initiates a transfer of the data to the control with a call to CBindStatusCallback<CATLControlWin>::Download(). CBindStatusCallback is an ATL wrapper class that wraps the IBindStatusCallback interface. In turn, IBindStatusCallback provides the actual asynchronous transfer by using the WinInet classes described in Chapter 24, "Programming Internet Applications with WinInet." 

CBindStatusCallback handles all of the details of the data transfer and only requires that you implement a callback function, which we'll call OnData. When you call Download, the ATL wrapper class links your callback to the IBindStatusCallback interface and starts the download. Note that, as with other types of callbacks, OnData must conform to a prototype; in this case, it must be declared as a ATL_PDATAAVAILABLE function. See the ATL documentation on the CBindStatusCallback interface for more information.

Listing 32.7 ATLCONTROLWIN.CPP?The TextDataPath Accessors Handle the Asynchronous Transfer

STDMETHODIMP CATLControlWin::get_TextDataPath(BSTR * pVal)

{

// return a copy of the member variable

*pVal = m_bstrTextDataPath.Copy();

return S_OK;

}

STDMETHODIMP CATLControlWin::put_TextDataPath(BSTR newVal)

{

HRESULT hResult = S_OK;

// copy the new string to the member variable

m_bstrTextDataPath = newVal;

// clear the data buffer

m_bstrText = _T("");

// start the asynchronous download of the data

CBindStatusCallback<CATLControlWin>::Download(this, OnData, m_bstrTextDataPath, 

m_spClientSite, FALSE);

// let the container know that the property has changed

this->SetDirty(TRUE);

// this->SetModifiedFlag(); <== MFC version

return hResult;

}



OnData, shown in Listing 32.8, is a very basic implementation of the asynchronous data transfer mechanism provided by the IBindStatusCallback interface and the CBindStatusCallback class. When new data arrives, CBindStatusCallback triggers OnData to handle it. In our case, OnData will append each new block of data to the m_bstrText member variable, and then call CaptionMethod to update the control's display.

Throughout the OnData implementation, note the use of the CBindStatusCallback members to determine the status of the current call to OnData. Also note the use of the ReadyState property to indicate to the container application that an asynchronous download is taking place and when it has finished. OnData fires events to advise the container of changes in its state.

Listing 32.8 ATLCONTROLWIN.CPP?Calling OnData when Data Arrives

//OnData will be used as a callback function by the CBindStatusCallback object.

//OnData will be called periodically with data from the asynchronous transfer

void CATLControlWin::OnData(CBindStatusCallback<CATLControlWin>* pbsc, BYTE* pBytes, 

DWORD dwSize)

{

// if we have not read any data yet

if(pbsc->m_dwTotalRead == 0)

{

// clear the buffer

m_bstrText = _T("");

// set the ready state of the control

m_lReadyState = READYSTATE_LOADING;

// let the container know that the property has changed

this->Fire_ReadyStateChange();

}

// add the data to our buffer

m_bstrText.Append((LPCSTR) pBytes); 

long lRetVal;

VARIANT varAlignment;

// initialize the variant

::VariantInit(&varAlignment);

// defer to the CaptionMethod implementation

this->CaptionMethod(m_bstrText, varAlignment, &lRetVal);

// if the function returned success

if(TRUE == lRetVal)

// let the control know that the property has changed

this->SetDirty(TRUE);

// this->SetModifiedFlag(); <== MFC version

// if there is nothing left

if(pbsc->m_dwAvailableToRead == 0)

{

// set the ready state of the control

m_lReadyState = READYSTATE_COMPLETE;

// let the container know that the property has changed

this->Fire_ReadyStateChange();

}

}

Making the Property Persistent

The crowning touch in your asynchronous property implementation is to add the TextDataPath property to the property map so that it will be persistent. The updated property map is shown in Listing 32.9. Do not add the ReadyState property to the persistence because its value is not valid across execution lifetimes.


Listing 32.9 ATLCONTROLWIN.H?Adding TextDataPath to the Property Map to Make It Persistent

. . .

BEGIN_PROPERTY_MAP(CATLControlWin)

// PROP_ENTRY("Description", dispid, clsid)

PROP_ENTRY("TextDataPath", dispidTextDataPath, CLSID_ATLControlWinPPG)

PROP_ENTRY("Alignment", dispidAlignment, CLSID_ATLControlWinPPG)

PROP_ENTRY("BackColor", DISPID_BACKCOLOR, CLSID_ATLControlWinPPG)

PROP_PAGE(CLSID_CColorPropPage)

END_PROPERTY_MAP()

. . .

Static and Dynamic Property Enumeration

Property enumeration restricts a property's values to a specific set of values. For example, a control that supports a property called TextAligment might use values of 0, 1, and 2 for left-, center-, and right-justified text, or it could use enumerated values named LeftJustified, RightJustified, and CenterJustified. Another example is a property used to determine what languages a control supports. A language-based property might be a good candidate for both a static set, say English and German, and a dynamic set, say for all the languages available on a particular machine.

Property enumeration makes it easier for script writers to drive your control because they can write natural-sounding expressions like theText.Justification = CenterJustified instead of relying on meaningless integer values.

You can create property enumerations in two ways. The simplest way is to use static enumeration, where the enumerations are defined in the control?s ODL or IDL file. As soon as the control is compiled, static enumerations stay fixed. The ATLControlWin version in Chapter 31 used this approach. The alternative, dynamic enumeration, is a little more complicated, but it enables you to change which enumerated values you support at runtime.

Supporting Dynamic Property Enumeration

Dynamic property enumeration is based on the interface IPerPropertyBrowsing, which provides a standardized way for containers to get lists of available enumerated values. The ATL Object Wizard won't add this interface to your control class automatically, so you'll have to add it yourself. The first step is to add the IPerPropertyBrowsingImpl class to your control inheritance structure:

public IPerPropertyBrowsingImpl<CATLControlWin>

Next, add the IPerPropertyBrowsing interface to the COM interface map:

COM_INTERFACE_ENTRY_IMPL(IPerPropertyBrowsing)

The final step is to implement the actual IPerPropertyBrowsing interface functions. First, add the function prototypes to the class declaration, as shown in Listing 32.10.

Listing 32.10 ATLCONTROLWIN.H?Four Functions that Implement the IPerPropertyBrowsing Interface on Your Control

. . .

STDMETHOD(MapPropertyToPage)(DISPID dispID, CLSID *pClsid);

STDMETHOD(GetPredefinedStrings)(DISPID dispID, CALPOLESTR *pCaStringsOut,

CADWORD *pCaCookiesOut);

STDMETHOD(GetPredefinedValue)(DISPID dispID, DWORD dwCookie, VARIANT* pVarOut);

STDMETHOD(GetDisplayString)(DISPID dispID,BSTR *pBstr);

. . .

Getting the Right Property Page

MapPropertyToPage (see Listing 32.11) is used to link a property to a specific control or system-defined property page. In this case, you return E_NOTIMPL if the requested dispid matches that of the Alignment property. By returning E_NOTIMPL, you are preventing the container application from displaying the property page associated with the property; instead, the container will use the property enumeration that you have implemented. The connection between the property and the property page is made in the property map macro in the class declaration. Remember that one of the parameters in the macro was the CLSID of the property page for the property.

Listing 32.11 ATLCONTROLWIN.CPP?Using MapPropertyToPage to Handle Translation Between a Requested Property and the Appropriate Page

STDMETHODIMP CATLControlWin::MapPropertyToPage(DISPID dispID, CLSID *pClsid)

{

// if this is the dispid property

if(dispID == dispidAlignment)

// defer to the property enumeration and not the property page

return E_NOTIMPL;

else

// defer to the base class implementation

return IPerPropertyBrowsingImpl<CATLControlWin>::

MapPropertyToPage(dispID, pClsid);

}

Getting the Enumerations' String Values

GetPredefinedStrings is the first function to be called (see Listing 32.12). When this method is called, the dispid of the property that is currently being referenced will be passed in. This method is called for all properties that the control supports, so take care when adding code. If the function is called with a dispid that the control owns, the control is required to create an array of strings and cookies. A cookie is just a 32-bit value that has some meaning to the control implementation. In this case, the cookies that you supply are the enumeration constants that you added in Chapter 31: EALIGN_LEFT, EALIGN_RIGHT, and EALIGN_CENTER. The strings are placed in a list from which the user of the control can select the appropriate value for the property.


GetPredefinedStrings uses string literals defined in AlignmentEnumerations.h for its strings. In your controls, consider using string tables; not only does this isolate the effect of changing the strings, but it makes it easier to internationalize your controls.

  
Listing 32.12 ATLCONTROLWIN.CPP?GetPredefinedStrings Returns the Strings Corresponding to the Available Enumerated Values

STDMETHODIMP CATLControlWin::GetPredefinedStrings(DISPID dispid,

CALPOLESTR * lpcaStringsOut, CADWORD * lpcaCookiesOut)

{

USES_CONVERSION;

HRESULT hResult = S_FALSE;

// we should have gotten two pointers if we didn't

if((lpcaStringsOut == NULL) || (lpcaCookiesOut == NULL))

// we are out of here

return E_POINTER;

// if this is the property that we are looking for

if(dispid == dispidAlignment)

{

ULONG ulElems = 3;

// allocate the memory for our string array

lpcaStringsOut->pElems = (LPOLESTR *) ::CoTaskMemAlloc(

sizeof(LPOLESTR) * ulElems);

// if we couldn't allocate the memory

if(lpcaStringsOut->pElems == NULL)

// we're out of here

return E_OUTOFMEMORY;

// allocate the memory for our cookie array

lpcaCookiesOut->pElems = (DWORD*) ::CoTaskMemAlloc(sizeof(DWORD*) * ulElems);

// if we couldn't allocate the memory

if (lpcaCookiesOut->pElems == NULL)

{

// free the string array

::CoTaskMemFree(lpcaStringsOut->pElems);

// exit the function

return E_OUTOFMEMORY;

}

// store the number of elements in each array

lpcaStringsOut->cElems = ulElems;

lpcaCookiesOut->cElems = ulElems;

// allocate the strings

lpcaStringsOut->pElems[0] = ATLA2WHELPER((LPWSTR)::CoTaskMemAlloc(

(lstrlen(EALIGN_LEFT_TEXT) + 1) * 2), EALIGN_LEFT_TEXT, 

lstrlen(EALIGN_LEFT_TEXT) + 1);

lpcaStringsOut->pElems[1] = ATLA2WHELPER((LPWSTR)::CoTaskMemAlloc(

(lstrlen(EALIGN_CENTER_TEXT) + 1) * 2), EALIGN_CENTER_TEXT,

lstrlen(EALIGN_CENTER_TEXT) + 1);

lpcaStringsOut->pElems[2] = ATLA2WHELPER((LPWSTR)::CoTaskMemAlloc(

(lstrlen(EALIGN_RIGHT_TEXT) + 1) * 2), EALIGN_RIGHT_TEXT,

lstrlen(EALIGN_RIGHT_TEXT) + 1);

// assign the cookie value

lpcaCookiesOut->pElems[0] = EALIGN_LEFT;

lpcaCookiesOut->pElems[1] = EALIGN_CENTER;

lpcaCookiesOut->pElems[2] = EALIGN_RIGHT;

hResult = S_OK;

}

return hResult;

}

Getting an Enumeration Value

GetPredefinedValue, shown in Listing 32.13, is called when the container's property browser needs the real enumeration value associated with a particular dispid and cookie. The value returned is the actual value that is stored in the property, not the string that was used to represent it. For our control, the returned value will always be a VT_I4, but if, for example, we were storing a string instead, we'd return a string variant. 

Listing 32.13 ATLCONTROLWIN.CPP?Using GetPredefinedValue to Map the Control-Defined Cookie into a Variant

STDMETHODIMP CATLControlWin::GetPredefinedValue(DISPID dispid, DWORD dwCookie,

VARIANT* 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;

}

Turning an Enumeration into a String

After the container calls GetPredefinedValue, it can set the property with the correct value?but it still needs to map that value to a human-readable string. GetDisplayString does just that: It returns the string that is associated with the current property setting.

Listing 32.14 ATLCONTROLWIN.CPP?Tying an Eumeration Back to a Text String with GetDisplayString

STDMETHODIMP CATLControlWin::GetDisplayString(DISPID dispid, BSTR* lpbstr)

{

USES_CONVERSION;

HRESULT hResult = S_FALSE;

// which property is it

switch(dispid)

{

case dispidAlignment:

{

switch(m_lAlignment)

{

case EALIGN_LEFT:

*lpbstr = ::SysAllocString(T2OLE(EALIGN_LEFT_TEXT));

break;

case EALIGN_CENTER:

*lpbstr = ::SysAllocString(T2OLE(EALIGN_CENTER_TEXT));

break;

case EALIGN_RIGHT:

*lpbstr = ::SysAllocString(T2OLE(EALIGN_RIGHT_TEXT));

break;

}

// set the return value

hResult = S_OK;

}

break;

}

return hResult;

}




The property browser already gets a string for every value when it calls GetPredefinedStrings, so GetDisplayString might seem a little redundant. You should have GetDisplayString because it can be implemented without implementing the other methods. Property types that do not use the standard property selection mechanism (for example, fonts) can use GetDisplayString to fill out a list in a dialog.

Optimizing Control Drawing

Optimized drawing enables you to create drawing objects, such as pens or brushes, once. Rather than removing them when you are finished drawing, you can cache them as control member variables and reuse them each time your control draws itself. The benefit is that you create drawing resources once for the lifetime of your control, rather than every time it draws.

If your control doesn't do a lot of custom drawing, optimized drawing might not boost its performance. There's also a size-versus-speed tradeoff between standard and optimized drawing: standard drawing doesn't use member variables for the drawing objects that are created and used, so it saves space at the expense of having to reallocate the drawing objects each time the control is drawn. Optimized drawing trades that space for the increased speed resulting from reusing graphic resources in the redraw loop.

There's an additional drawback to optimized drawing: the container that your control ends up in might not support it. Your controls must continue to support standard drawing functionality, switching to optimized only if it is available.


ATL's optimized drawing capabilities are only a subset of those defined in the OC 96 specification. The OC 96 specification further breaks optimized drawing into aspects. For more information on aspect drawing, please see the OC 96 Specification that ships with the ActiveX SDK.



In Chapter 31, you learned how to implement standard drawing so that your control could draw itself. Supporting optimized drawing is fairly simple, as you'll see.

First, you need to check whether or not the container supports optimized drawing. The bOptimize member of the ATL_DRAWINFO structure passed to your draw method will be FALSE if the container can't handle optimized drawing. In that case, you have to clean up all of your allocated resources and restore any original values. If the container does support optimized drawing, you ignore the cleanup and reuse the allocated resources the next time around. Listing 32.15 shows the modified OnDraw function.


Listing 32.15 ATLCONTROLWIN.CPP?Optimizing Drawing by Caching Drawing Resources?if the Container Supports It

HRESULT CATLControlWin::OnDraw(ATL_DRAWINFO & di)

{

. . .

// The container does not support optimized drawing.

if(!di.bOptimize)

{

// select the old brush back

::SelectObject(di.hdcDraw, hOldBrush);

// destroy the brush we created

::DeleteObject(hBrush);

// clear the brush handles

hBrush = hOldBrush = NULL;

}

return S_OK;

}



To clean up the cached variables, you must add a message handler, OnDestroy, for the Windows message WM_DESTROY. Also add an OnDestroy function prototype to the class declaration, as shown in Listing 32.16. OnDestroy is used to clean up the drawing resources if any are still allocated when the control is destroyed. This should happen only if the container supports optimized drawing; if not, the resources should be freed normally when drawing is done.

Listing 32.16 ATLCONTROLWIN.H?Adding WM_DESTROY Support to Enable Cleanup

BEGIN_MSG_MAP(CATLControlWin)

MESSAGE_HANDLER(WM_PAINT, OnPaint)

MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)

MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)

MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)

MESSAGE_HANDLER(WM_DESTROY, OnDestroy)

END_MSG_MAP()

. . .

LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

. . .

The last step is to add the OnDestroy implementation, which will clean up the resources if they are still allocated (see Listing 32.17).

Listing 32.17 ATLCONTROLWIN.CPP?Cleanup Performed by OnDestroy

LRESULT CATLControlWin::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled)

{

// if there is an old brush

if(hOldBrush)

{

// get the DC

HDC hDC = this->GetDC();

// select the old brush back

::SelectObject(hDC, hOldBrush);

// release the DC

this->ReleaseDC(hDC);

}

// if we created a brush

if(hBrush)

// destroy the brush we created

::DeleteObject(hBrush);

return TRUE;

}

Adding Clipboard and Drag-and-Drop Support

The basic OLE Clipboard and Drag-and-Drop interfaces are only partially implemented by the default ATL control implementation. As with the IPerPropertyBrowsing interface, you must provide the remaining required interfaces yourself. Supporting the Clipboard and Drag-and-Drop in your control adds polish and ease-of-use for the end user with relatively little effort on your part.

Talking to the Clipboard

Clipboard support in ActiveX controls comes from the IDataObject and IEnumFORMATETC interfaces. IDataObject provides an interface for the Clipboard to request data from your control, and IEnumFORMATETC is the interface used by other applications to determine what data types your control's IDataObject interface supports. In a basic ATL control project, only the IDataObject interface is supported. The IEnumFORMATETC interface must be added.

Before adding the specific interfaces required by ActiveX to enable Clipboard transfers, you must first decide which keystroke combinations will be used to initiate the cut, copy, or paste operations. You should either use Ctrl+X, Ctrl+C, and Ctrl+V or Shift+Delete, Ctrl+Insert, and Shift+Insert for cut, copy, and paste respectively.

Watching for the Editing Keys

To trap these keystrokes in your control, you'll need a message handler for the WM_KEYDOWN message in the form of a method called OnKeyDown (see Listing 32.18).


Listing 32.18 ATLCONTROLWIN.H?Adding WM_KEYDOWN to the Message Map and then Adding an OnKeyDown Handler

. . .

BEGIN_MSG_MAP(CATLControlWin)

MESSAGE_HANDLER(WM_PAINT, OnPaint)

MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)

MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)

MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)

MESSAGE_HANDLER(WM_DESTROY, OnDestroy)

MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)

END_MSG_MAP()

. . .

LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

LRESULT OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

. . .


The OnKeyDown method, shown in Listing 32.19, looks for the predefined editing keystroke combinations listed in the preceding paragraph; if the method finds one, it calls the inherited data transfer functions to complete the requested Clipboard operation. If the user requested a Cut operation, OnKeyDown fires the Change and ViewChange events to redisplay the control after clearing its data.


Listing 32.19 ATLCONTROLWIN.CPP?OnKeyDown, Calling the Inherited IDataObject Interfaces when the User Requests a Cut or Copy Request

LRESULT CATLControlWin::OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam,

BOOL & bHandled)

{

UINT nChar = wParam;

UINT nRepCnt = LOWORD(lParam);

UINT nFlags = HIWORD(lParam);

// 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)

{

// COPY

case 0x43: // 'C'

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;

}

break;

// CUT

case 0x58: // 'X'

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

delete [] m_lptstrCaption;

// NULL terminate the string reference

m_lptstrCaption = new TCHAR[1];

m_lptstrCaption[0] = '\0';

// fire the global change event

this->FireChange();

// force the control to repaint itself

this->FireViewChange();

// we don't need to pass this key to the base implementation

bHandled = TRUE;

}

break;

}

return TRUE;

}

Supporting the Clipboard

There are two types of Clipboard support: a Clipboard source and a Clipboard target. When you're a Clipboard source, your control can copy data to the Clipboard. Having your control be a Clipboard target means that it can accept data from the Clipboard and use it as though the user had provided it directly.

Windows supports a number of built-in formats for transferring data via the Clipboard. Our control will send and accept data by using the CF_TEXT format, which is the built-in format for transferring ANSI text. Don't despair if you're writing controls that support other types of data; Windows also includes support for bitmaps, TIFF files, device-independent bitmaps, Windows metafiles, SYLK-format tabular data, files, and a variety of other formats. (As you'll see later in this section, you can even define your own custom formats, which other Clipboard clients can use for rich data exchange.)

Any code that wants to use the OLE-enabled Clipboard must support two special COM interfaces to act as a Clipboard source or target: IDataObject and IEnumFORMATETC.





The combination of these two interfaces makes it possible for a Clipboard client to use Windows' built-in data formats for exchange. When your control receives data from the Clipboard, you can use IDataObject::EnumFormatEtc to find the most appropriate format; likewise, any other client reading data you put on the Clipboard can do the same.

ATL-based controls already inherit from IDataObject, but the IEnumFORMATETC interface must be added, as shown in Listing 32.20. Because the control has to provide new versions of some functions of the IDataObject and the IEnumFORMATETC interfaces, you'll need to add function prototypes to the class declaration of your control. Note that you only need to add prototypes for the functions that you're going to override; the remainder can use their default implementations.

Listing 32.20 ATLCONTROLWIN.H?Adding the Clipboard's COM Interfaces to the CATLControlWin Class Declaration

. . .

public IPerPropertyBrowsingImpl<CATLControlWin>,

public IEnumFORMATETC

{

public:

. . .

// IDataObject

STDMETHOD(GetData)(LPFORMATETC, LPSTGMEDIUM);

STDMETHOD(EnumFormatEtc)(DWORD, LPENUMFORMATETC*);

// IEnumFORMATETC

STDMETHOD(Next)(ULONG celt, FORMATETC __RPC_FAR * rgelt,

ULONG __RPC_FAR * pceltFetched);

STDMETHOD(Skip)(ULONG celt);

STDMETHOD(Reset)(void);

STDMETHOD(Clone)(IEnumFORMATETC __RPC_FAR *__RPC_FAR * ppenum);

. . .



Our IDataObject implementation is straightforward and involves only two functions, as shown in Listing 32.21. The rest of the IDataObject interface functions are already implemented by the ATL class IDataObjectImpl. GetData will, if the format type matches that of a format that your control understands, copy the control?s current data to the STGMEDIUM parameter that is passed to the control. If the format type is unrecognized, you must defer to the base class implementation of GetData. This deferral is very important because the CComControlBase ATL class implements the IDataObject::GetData function for copying a range of standard Windows data formats.

EnumFormatEtc is used to return an IEnumFORMATETC reference to the requesting application so that it can determine what types of data formats are supported by the control implementation. Again, you must defer to the base class implementation to ensure that the control functions correctly.

Listing 32.21 ATLCONTROLWIN.CPP?IDataObject Interface Implementation

. . .

STDMETHODIMP CATLControlWin::GetData(LPFORMATETC lpFormatEtc, 

LPSTGMEDIUM lpStgMedium)

{

// if this is a format that we can deal with

if(lpFormatEtc->cfFormat == CF_TEXT && lpFormatEtc->tymed & TYMED_HGLOBAL)

{

// get a copy of the current stgmedium

this->CopyStgMedium(lpStgMedium, &sTextStgMedium, CF_TEXT);

return S_OK;

}

else

return IDataObjectImpl<CATLControlWin>::GetData(lpFormatEtc, lpStgMedium);

}

STDMETHODIMP CATLControlWin::EnumFormatEtc(DWORD dwDirection,

LPENUMFORMATETC* ppenumFormatEtc)

{

// we support "get" operations

if(dwDirection == DATADIR_GET)

{

// make the assignment

*ppenumFormatEtc = (IEnumFORMATETC *) this;

// increment the reference count

(*ppenumFormatEtc)->AddRef();

// return success

return S_OK;

}

return IDataObjectImpl<CATLControlWin>::EnumFormatEtc(

dwDirection, ppenumFormatEtc);

}

. . .


Microsoft specifies that enumerators should return available formats in the same order in which you put multiple formats on the Clipboard. For example, if your control puts both a DIB and a metafile on the Clipboard, the enumerator should return both types in the same order.


Listing 32.22 ATLCONTROLWIN.CPP?IEnumFORMATETC Interface Implementation

STDMETHODIMP CATLControlWin::Next(ULONG celt, FORMATETC_RPC_FAR * rgelt,

ULONG RPC_FAR * pceltFetched)

{

// if we are at the beginning of the enumeration

if(ulFORMATETCElement == 0 && celt > 0)

{

// copy all of the members

rgelt->cfFormat = CF_TEXT;

rgelt->ptd = NULL;

rgelt->dwAspect = 0;

rgelt->lindex = -1;

rgelt->tymed = TYMED_HGLOBAL;

// if the caller wants to know how many we copied

if(pceltFetched)

*pceltFetched = 1;

// increment the counter

ulFORMATETCElement++;

// return success

return S_OK;

}

else

// return failure

return S_FALSE;

}

STDMETHODIMP CATLControlWin::Skip(ULONG celt)

{

// move the counter by the number of elements supplied

ulFORMATETCElement += celt;

// return success

return S_OK;

}

STDMETHODIMP CATLControlWin::Reset(void)

{

// reset to the beginning of the enumerator

ulFORMATETCElement = 0;

// return success

return S_OK;

}

STDMETHODIMP CATLControlWin::Clone(

IEnumFORMATETC_RPC_FAR *__RPC_FAR * /*ppenum*/)

{

return E_NOTIMPL;

}

Exchanging Data with the Clipboard

In addition to the code that you added to trap the keystrokes and to support the needed COM interfaces, you need to add methods for transferring data to and from the Clipboard. For this control, there will be a total of five methods.


You might ask why there are four helper functions. As you'll see in the section on Drag-and-Drop support, you send and receive data the same way for drag operations and Clipboard operations. The transfer process involves the same set of COM interfaces, so GetDataFromTransfer and PrepareDataForTransfer can be used in both cases.

Putting Data on the Clipboard

Now that the control supports the needed interfaces, let's examine the specifics of making ATLControlWin a Clipboard source. First of all, we'll need some additional member variables. sTextFormatEtc and sTextStgMedium will hold the format and store the text when the user copies data to the Clipboard?we'll need that data when the paste target requests it. ulFORMATETCElement is the internal reference counter for the IEnumFORMATETC interface. Add these declarations and function prototypes for CopyDataToClipboard, PrepareDataForTransfer, and CopyStgMedium, to the CATLControlWin class declaration. The relevant code is in Listing 32.23.

Listing 32.23 ATLCONTROLWIN.H?Helper Functions and Member Variables for Clipboard Support

. . .

void CopyDataToClipboard(void);

void PrepareDataForTransfer(void);

void CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,

LPSTGMEDIUM lpSourceStgMedium, CLIPFORMAT cfSourceFormat);

ULONG ulFORMATETCElement;

private:

FORMATETC sTextFormatEtc;

STGMEDIUM sTextStgMedium;

};


sTextFormatEtc and sTextStgMedium must be initialized in the constructor of your class so that they will not contain meaningless data the first time that they are used (see Listing 32.24).

Listing 32.24 ATLCONTROLWIN.H?Member Initialization in the Class Constructor

. . .

// set the initial state of the ReadyState property

m_lReadyState = READYSTATE_LOADING;

// set to the first element

ulFORMATETCElement = 0;

// clear the storage medium

sTextStgMedium.hGlobal = NULL;

}

. . .


When the user initiates a data transfer to the Clipboard, a reference to the source?s IDataObject interface is placed on the Clipboard. At that time, you have to take a snapshot of the control's contents and place it in a STGMEDIUM object. You do this because the data might not be copied from the Clipboard immediately, and the data needs to reflect the state of the control when the copy operation was initiated rather than when the paste operation takes place.

After the IDataObject interface is on the Clipboard, you simply wait until someone requests the data. If the request is for a format that you support, copy the data from your snapshot (in this case, the sTextStgMedium member) to the STGMEDIUM structure that was passed to you.

Now turn to the member functions that actually take the data snapshot and copy it to the Clipboard. CopyDataToClipboard is called to initiate the Clipboard transfer and is shown in Listing 32.25. The code first checks to see whether or not it already owns the Clipboard, and then it prepares the caption data for transfer to the clipboard by calling PrepareDataForTransfer. Finally, if the Clipboard is owned by another program, it flushes whatever data is on the Clipboard and passes its IDataObject reference to the Clipboard for later use.

Listing 32.25 ATLCONTROLWIN.CPP?CopyDataToClipboard Helper Function Implementation

void CATLControlWin::CopyDataToClipboard(void)

{

BOOL bHaveClipboard = TRUE;

// if we don't have an IDataObject on the clipboard?

if(::OleIsCurrentClipboard((IDataObject *) this) != S_OK)

// set the flag

bHaveClipboard = FALSE;

// put data in the storage

this->PrepareDataForTransfer();

// if we don't have the clipboard

if(!bHaveClipboard)

{

// clear the clipboard

::OleFlushClipboard();

// put the data on the clipboard

::OleSetClipboard(reinterpret_cast<IDataObject*>

(static_cast<IDataObjectImpl<CATLControlWin>*>(this))); 

}

}


PrepareDataForTransfer actually does the work of copying data from your control to the STGMEDIUM ?snapshot? that will hold it on the Clipboard. As shown in Listing 32.26, PrepareDataForTransfer starts by allocating and locking a block of global memory to hold the caption as an ANSI string. Next, it fills in the FORMATETC and STGMEDIUM structures to reflect the correct data type. Finally, it releases the STGMEDIUM's storage, if any, and replaces it with the allocated global block. The effect of all this is to create a copy of the string pointed to by the control's sTextStgMedium member.

Listing 32.26 ATLCONTROLWIN.CPP?PrepareDataForTransfer Helper Function Implementation

void CATLControlWin::PrepareDataForTransfer(void)

{

// get the length of the data to copy

long lLength = lstrlen(m_lptstrCaption) + 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_lptstrCaption[lCount];

// null terminate the string

lpTempBuffer[lCount] = '\0';

// unlock the memory

::GlobalUnlock(hGlobal);

// set all of the members

sTextFormatEtc.cfFormat = CF_TEXT;

sTextFormatEtc.ptd = NULL;

sTextFormatEtc.dwAspect = 0;

sTextFormatEtc.lindex = -1;

sTextFormatEtc.tymed = TYMED_HGLOBAL;

// if we have already allocated the data

if(sTextStgMedium.hGlobal)

// release it

::ReleaseStgMedium(&sTextStgMedium);

sTextStgMedium.tymed = TYMED_HGLOBAL;

sTextStgMedium.hGlobal = hGlobal;

sTextStgMedium.pUnkForRelease = NULL;

}

Lastly, CopyStgMedium, shown in Listing 32.27, is a general- purpose helper function for copying one STGMEDIUM to another. Our IDataObject::GetData method (shown earlier in Listing 32.21) calls CopyStgMedium, which we'll use later when we implement custom formats in "Handling Custom Clipboard and Drag-and-Drop Formats." This function is useful because it allocates a new global data object instead of copying the reference to the existing object. This keeps the copy available even if the source STGMEDIUM is freed.

Listing 32.27 ATLCONTROLWIN.CPP?CopyStgMedium Helper Function Implementation

void CATLControlWin::CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,

LPSTGMEDIUM lpSourceStgMedium, CLIPFORMAT cfSourceFormat)

{

// copy the stgmedium members

lpTargetStgMedium->tymed = lpSourceStgMedium->tymed;

lpTargetStgMedium->pUnkForRelease = lpSourceStgMedium->pUnkForRelease;

lpTargetStgMedium->hGlobal = ::OleDuplicateData(lpSourceStgMedium->hGlobal,

cfSourceFormat, GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT);

}

Getting Data from the Clipboard

The opposite of being a Clipboard source is being a Clipboard target. To be a Clipboard target, you first need to update the CATLControlWin class declaration to include two new helper functions, GetDataFromClipboard and GetDataFromTransfer, as shown in Listing 32.28.

Listing 32.28 ATLCONTROLWIN.H?Clipboard Target Support Helper Function Prototypes

. . .

void CopyDataToClipboard(void);

void PrepareDataForTransfer(void);

void GetDataFromClipboard(void);

void GetDataFromTransfer(IDataObject * ipDataObj);

void CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,

LPSTGMEDIUM lpSourceStgMedium, CLIPFORMAT cfSourceFormat);

. . .


Getting data from the Clipboard is almost as simple as putting the data on the Clipboard in the first place. The primary method is GetDataFromClipboard, which gets the data from the Clipboard and transfers it to the control. As you can see in Listing 32.29, the function first checks the Clipboard to see whether or not the control already owns it. If the control does own the Clipboard, it refreshes the control?s data with the data that is stored in the cached STGMEDIUM structure. The function is written this way because the data stored in the control might have changed since it was originally pasted to the Clipboard.

If another process owns the Clipboard, GetDataFromClipboard gets a reference to the owner's IDataObject and passes it on to the GetDataFromTransfer function.

Listing 32.29 MFCCONTROLWINCTL.CPP?GetDataFromClipboard Implementation

void CATLControlWin::GetDataFromClipboard(void)

{

// get an IDataObject pointer

IDataObject * ipClipboardDataObj = NULL;

// do we have an IDataObject on the clipboard?

if(::OleIsCurrentClipboard((IDataObject *) this) == S_OK)

{

// get the global data for this format and lock down the memory

LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(sTextStgMedium.hGlobal);

// if we have a string

if(m_lptstrCaption)

{

// delete the existing string

delete [] m_lptstrCaption;

// clear the reference just to be safe

m_lptstrCaption = NULL;

}

// allocate a new string

m_lptstrCaption = new TCHAR[lstrlen(lpTempBuffer) + 1];

// assign the string to our member variable

lstrcpy(m_lptstrCaption, lpTempBuffer);

// unlock the memory

::GlobalUnlock(sTextStgMedium.hGlobal);

return;

}

else if(::OleGetClipboard(&ipClipboardDataObj) == S_OK)

{

// transfer the data to the control

this->GetDataFromTransfer(ipClipboardDataObj);

// release the IDataObject

ipClipboardDataObj->Release();

}

}


GetDataFromTransfer, which appears in Listing 32.30, begins by requesting the IEnumFORMATETC interface from the provided IDataObject and cycling through all of the supported formats to look for CF_TEXT. Upon finding the appropriate format, it requests the data from IDataObject::GetData, supplying a FORMATETC and a STGMEDIUM structure. GetData puts the data in the storage and fills in the FORMATETC to specify what data is being returned and how it's stored. Next, the newly retrieved caption is loaded into the control, and the STGMEDUIM is released. Finally, GetDataFromTransfer releases the interface pointer for the enumerator, and, if it found a compatible format, forces the control to repaint itself with the new caption.


If your control can handle multiple data formats?for example, if you want to accept rich text if it's available and CF_TEXT if it's not?you can compare the cfFormat field of the enumerator's returned value to look for the types in your preferred order.


Listing 32.30 MFCCONTROLWINCTL.CPP?GetDataFromTransfer Implementation

void CATLControlWin::GetDataFromTransfer(IDataObject * ipDataObj)



IEnumFORMATETC * ipenumFormatetc;

BOOL bFound = FALSE;

// get a FORMATETC enumerator

if(ipDataObj->EnumFormatEtc(DATADIR_GET, &ipenumFormatetc) == S_OK)

{

// reset the enumerator just to be safe

ipenumFormatetc->Reset();

FORMATETC etc;

// while there are formats to enumerate

while(ipenumFormatetc->Next(1, &etc, NULL) == S_OK)

{

// is this a format that we are looking for?

if(etc.cfFormat == CF_TEXT && etc.tymed & TYMED_HGLOBAL)

{

STGMEDIUM sStgMediumData;

// get the data from the stgmedium

if(ipDataObj->GetData(&etc, &sStgMediumData) == S_OK)

{

// get the global data for this format

// and lock down the memory

LPTSTR lpTempBuffer = (LPTSTR)

::GlobalLock(sStgMediumData.hGlobal);

// if we have a string

if(m_lptstrCaption)

{

// delete the existing string

delete [] m_lptstrCaption;

// clear the reference just to be safe

m_lptstrCaption = NULL;

}

// allocate a new string

m_lptstrCaption = new TCHAR[lstrlen(lpTempBuffer) + 1];

// assign the string to our member variable

lstrcpy(m_lptstrCaption, lpTempBuffer);

// unlock the memory

::GlobalUnlock(sStgMediumData.hGlobal);

// release the storage medium

::ReleaseStgMedium(&sStgMediumData);

// indicate success

bFound = TRUE;

}

}

}

// release the enumerator

ipenumFormatetc->Release();

}

// if we found a format

if(bFound == TRUE)

// force the control to repaint itself

this->FireViewChange();

}


You might have noticed that the version of OnKeyDown shown in Listing 32.19 didn't support pasting from the Clipboard. The code in Listing 32.31 shows the changes needed to support pasting with either Ctrl+V or Shift+Insert. In either case, OnKeyDown calls GetDataFromClipboard to get the data and forces the control to update by firing a message with FireViewChange.

Listing 32.31 ATLCONTROLWIN.CPP?OnKeyDown Implementation

...

switch(nChar)

{

// PASTE

case 0x56: // 'V'

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 repaint itself

this->FireViewChange();

// we don't need to pass this key to the base implementation

bHandled = TRUE;

}

break;

// COPY or PASTE

case 0x43: // 'C'

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 repaint itself

this->FireViewChange();

// we don't need to pass this key to the base implementation

bHandled = TRUE;

}

break;

...

Supporting Drag-and-Drop

The fundamentals of supporting Drag-and-Drop are very similar to those needed for Clipboard support. Drag-and-Drop relies on the same set of interfaces for the actual data transfer, but it adds two new interfaces: IDropSource and IDropTarget. Drag sources?controls that create data that can be dropped onto another application?need to be inherited from IDropSource. The corresponding IDropTarget is for controls that can act as drop targets; targets accept data that has been dropped from another application or control.

Because Drag-and-Drop is essentially a Clipboard transfer?with fewer steps involved?Drag-and-Drop uses the same built-in data formats as Clipboard transfers, and the overall implementation is quite similar.


If it doesn't make sense for your control to be only a drag target or drag source but not both, don't worry. The two are independent, so you can implement either or both.

Enabling a Control as a Drag-and-Drop Source

The first part of your implementation is to enable the control as a drag source. To act as a drag source, the control must implement the IDropSource interface, in addition to the IDataObject and IEnumFORMATETC interfaces that you implemented in the "Supporting the Clipboard" section.

ATLControlWin inherits from IDropSource in the same manner as the other COM interfaces that you've added previously. Because the interface consists of only two methods, the implementation is easy. QueryContinueDrag is called by the system event handler so that you can tell it whether or not to register the drop event and send it to the target. GiveFeedback is called during the drag, so you can change the displayed cursor or otherwise indicate to the user that a drag is in progress. The necessary changes to ATLControlWin.h are shown in Listing 32.32.

Listing 32.32 ATLCONTROLWIN.H?IDropSource Interface Added to the CATLControlWin Class Declaration

. . .

public IEnumFORMATETC,

public IDropSource

{

public:

CATLControlWin()

. . .

COM_INTERFACE_ENTRY(IEnumFORMATETC)

COM_INTERFACE_ENTRY(IDropSource)

END_COM_MAP()

. . .

// IEnumFORMATETC

. . .

// IDropSource

STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD dwKeyState);

STDMETHOD(GiveFeedback)(DWORD dwEffect);

. . .


Before you can actually initiate a drag, you need to know when the user presses the left mouse button in your control. The easiest way to do this is to catch the WM_LBUTTONDOWN message, so you'll need a message handler for it. Add WM_LBUTTONDOWN message handler called OnLButtonDown to the message map, as shown in Listing 32.33.

Listing 32.33 ATLCONTROLWIN.H?WM_LBUTTONDOWN and OnLButtonDown Message Handler Added to the Class Declaration of the Control

. . .

BEGIN_MSG_MAP(CATLControlWin)

. . . 

MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)

END_MSG_MAP()

. . .

LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

LRESULT OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

. . .


The OnLButtonDown implementation, as shown in Listing 32.34, is similar to the CopyDataToClipboard method shown in Listing 32.25. OnLButtonDown prepares the data for its transfer and calls the Win32 DoDragDrop function to start the drag. Note that we always pass DROPEFFECT_COPY as the drop modifier for DoDragDrop; you could check for modifier keys and pass a different drop modifier to enable users to modify what happens on a drag.

Listing 32.34 ATLCONTROLWIN.CPP?OnLButtonDown Implementation

LRESULT CATLControlWin::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam,

BOOL & bHandled)

{

// Un-comment these parameters if you need them

// UINT nFlags = wParam;

// short sHor = (short) LOWORD(lParam);

// short sVer = (short) HIWORD(lParam);

// call the common data preparation function

this->PrepareDataForTransfer();

DWORD dwDropEffect = DROPEFFECT_NONE;

// start the Drag and Drop operation

::DoDragDrop(reinterpret_cast<IDataObject*>

(static_cast<IDataObjectImpl<CATLControlWin>*>(this)),

(IDropSource *) this, DROPEFFECT_COPY, &dwDropEffect);

return TRUE;

}


The final step in making the control a drag source is to implement the IDropSource interface, the code for which is shown in Listing 32.35

Listing 32.35 ATLCONTROLWIN.CPP?IDropSource Implementation

STDMETHODIMP CATLControlWin::QueryContinueDrag(BOOL fEscapePressed,

DWORD dwKeyState)

{

// if the left button has been released

if(!(dwKeyState & MK_LBUTTON))

// it is OK to drop

return DRAGDROP_S_DROP;

else

// return success

return S_OK;

}

STDMETHODIMP CATLControlWin::GiveFeedback(DWORD dwEffect)

{

// use the default cursors

return DRAGDROP_S_USEDEFAULTCURSORS;

}


QueryContinueDrag is used to tell OLE how the drag operation should be handled when the state of the keyboard or mouse changes. This might result when the user pressed Esc, or when the left mouse button was released. In this case, if the left mouse button is no longer being held down, you declare the drop to be complete. Otherwise, you exit, because the drag is still in progress.

GiveFeedback enables you to change the cursor displayed during a drop operation. For example, you might display different cursors when modifier keys are held during the drag, or you might change the cursor when the drag passes over a screen region that's not a legal target. In this case, we use the default cursors.

Enabling a Control as a Drag-and-Drop Target

To qualify as a drag target, a control must implement the IDropTarget interface and register itself as a target. By the time you've implemented Clipboard and drag source support, adding drag target support requires very little work. You'll need to make your control inherit from the IDropTarget interface in the same fashion as the other interfaces; you'll also need to support the DragEnter, DragOver, DragLeave, and Drop interface methods. The needed changes are shown in Listing 32.36.

Listing 32.36 ATLCONTROLWIN.H?IDropTarget Interface Added to the CATLControlWin Class Declaration

. . .

public IDropSource,

public IDropTarget

{

public:

CATLControlWin()

. . .

COM_INTERFACE_ENTRY(IDropSource)

COM_INTERFACE_ENTRY(IDropTarget)

END_COM_MAP()

. . .

// IDropSource

STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD dwKeyState);

STDMETHOD(GiveFeedback)(DWORD dwEffect);

// IDropTarget

STDMETHOD(DragEnter)(LPDATAOBJECT pDataObject, DWORD dwKeyState, POINTL pt,

LPDWORD pdwEffect);

STDMETHOD(DragOver)(DWORD dwKeyState, POINTL pt, LPDWORD pdwEffect);

STDMETHOD(DragLeave)(void);

STDMETHOD(Drop)(LPDATAOBJECT pDataObject, DWORD dwKeyState, POINTL pt,

LPDWORD pdwEffect);

. . .

Registering and Unregistering the Target
In addition to supporting the required ActiveX interfaces, you have to tell other, potentially non-ActiveX programs, that your control can be a target. To accomplish this, register the control as a drop target with Windows by calling the Win32 RegisterDragDrop function. Because you want the control to be registered when it's created, add a handler for the WM_CREATE message to the ATLControlWin message map, as shown in Listing 32.37.

Listing 32.37 ATLCONTROLWIN.H?WM_CREATE Message Handler

. . .

MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)

MESSAGE_HANDLER(WM_CREATE, OnCreate)

END_MSG_MAP()

. . .

LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled);

. . .


The OnCreate handler implementation calls RegisterDragDrop with the control's window handle, as shown in Listing 32.38

Listing 32.38 ATLCONTROLWIN.CPP?OnCreate Implementation

LRESULT CATLControlWin::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam,

BOOL & bHandled)

{

// if we have a window handle

if(m_hWnd)

// register the control as a drag and drop target

::RegisterDragDrop(m_hWnd, (IDropTarget *) this);

return TRUE;

}


Correspondingly, when the control is being destroyed, you need to call RevokeDragDrop to unregister the control as a valid drop target. You can add a call to RevokeDragDrop to the control's OnDestroy function (refer to Listing 32.17.) The added code is shown in Listing 32.39.

Listing 32.39 ATLCONTROLWIN.CPP?OnDestroy Implementation Updated to Revoke the Control as a Valid Drop Target

LRESULT CATLControlWin::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam,

BOOL & bHandled)

{

// if we have a window handle

if(m_hWnd)

// revoke the control as a drag and drop target

::RevokeDragDrop(m_hWnd);

. . .
Implementing IDropTarget
To make your control a real drag target, you must implement four functions from the IDropTarget interface. DragEnter, shown in Listing 32.40, is called when a drag enters your control. Your code must tell OLE whether or not your control can accept data from the current drag operation. The code first confirms that the mouse or keyboard state is appropriate?in this case, the left mouse button must be held down. Next, DragEnter uses the IEnumFORMATETC interface to see whether or not the IDataObject that was passed to the function contains any formats that the control can understand. If the data can be used, it returns DROPEFFECT_COPY to tell OLE that the drag is acceptable; in any other case, it returns DROPEFFECT_NONE to indicate that it's not.

Listing 32.40 ATLCONTROLWIN.CPP?DragEnter: Allowing You to Decide Whether to Accept or Reject a Drag

STDMETHODIMP CATLControlWin::DragEnter(LPDATAOBJECT pDataObject, DWORD dwKeyState,

POINTL pt, LPDWORD pdwEffect)

{

// if the left mouse button is being held down

if(dwKeyState & MK_LBUTTON)

{

IEnumFORMATETC * ipenumFormatetc;

BOOL bFound = FALSE;

// get a FORMATETC enumerator

if(pDataObject->EnumFormatEtc(DATADIR_GET, &ipenumFormatetc) == S_OK)

{

// reset the enumerator just to be safe

ipenumFormatetc->Reset();

FORMATETC etc;

// while there are formats to enumerate

while(ipenumFormatetc->Next(1, &etc, NULL) == S_OK && !bFound)

{

// is this a format that we are looking for?

if(etc.cfFormat == CF_TEXT && etc.tymed & TYMED_HGLOBAL)

bFound = TRUE;

}

// release the enumerator

ipenumFormatetc->Release();

}

// is there a text format available

if(bFound)

*pdwEffect = DROPEFFECT_COPY;

// everything else we can't deal with

else

*pdwEffect = DROPEFFECT_NONE;

}

else

// not the left mouse

*pdwEffect = DROPEFFECT_NONE;

// return success

return S_OK;

}


DragOver, shown in Listing 32.42, is used to test the current state of the drag operation while it is over the control. This implementation is very basic. One could, however, change the method to restrict the drag operation. You might check the POINTL structure that was passed in and compare it to various locations of the control. As another example, a grid or "spreadsheet" control might enable only text data to be dropped on the headings but both text and numeric data while over the columns.

Listing 32.41 ATLCONTROLWIN.CPP? Constraining the Drag, Based on the Mouse Coordinates

STDMETHODIMP CATLControlWin::DragOver(DWORD dwKeyState, POINTL pt,

LPDWORD pdwEffect)

{

// if the left mouse button is being held down

if(dwKeyState & MK_LBUTTON)

// copy

*pdwEffect = DROPEFFECT_COPY;

else

// not the left mouse

*pdwEffect = DROPEFFECT_NONE;

// return success

return S_OK;

}


DragLeave is used to clean up any state information that might have been created when DragEnter was invoked. If you don't allocate anything during DragLeave, just return E_NOTIMPL, as we do for ATLControlWin.

Drop (see Listing 32.43) is the last function that you need to implement. It actually copies the data from the source IDataObject to the control by using the GetDataFromTransfer method that we implemented for Clipboard support.

Listing 32.42 ATLCONTROLWIN.CPP?IDropTarget Interface Implementation

STDMETHODIMP CATLControlWin::Drop(LPDATAOBJECT pDataObject, DWORD dwKeyState,

POINTL pt, LPDWORD pdwEffect)

{

// transfer the data to the control

this->GetDataFromTransfer(pDataObject);

// return success

return S_OK;

}


That's it! If all you want is basic Drag-and-Drop support, you're done. You can also support custom formats, however, with a few more lines of code.

Handling Custom Clipboard and Drag-and-Drop Formats

In addition to the standard Windows formats, applications and controls can define their own custom data formats for data exchange. For example, Intergraph's Imagineer computer-aided design software defines custom formats for 2-D and 3-D drawings. When transferring data via Drag-and-Drop or the Clipboard, Imagineer supplies data in metafile, DIB, and its own custom format. Applications that understand the Imagineer formats can use them; applications that don't can use the metafile or DIB version of the data instead. 

Support for custom data formats is independent of the mechanism used to initiate the data transfer. Because we isolated the methods used to prepare data for transfer from those that actually do the transferring, we need make only one set of changes to accommodate custom Clipboard and Drag-and-Drop operations.

In preceding sections of this chapter, you implemented support for transferring ATLControlWin's caption as plain text. Next you examine what would be required to transfer the custom Alignment property along with the Caption.

Registering a Custom Format

Windows already provides predefined IDs for the common CF_XXX formats that it provides. To make your custom formats visible, you'll have to register them with Windows. This requires you to keep track of the ID number of the registered custom format; add a member variable named m_uiCustomFormat for this purpose. In addition, you also need an extra FORMATETC/STGMEDIUM pair so that you can store your custom data and text data simultaneously. Listing 32.43 shows the required change.

Listing 32.43 ATLCONTROLWIN.H?Custom Data Format Member Variables

. . .

private:

FORMATETC sTextFormatEtc;

STGMEDIUM sTextStgMedium;

// custom format storage variables

UINT m_uiCustomFormat;

FORMATETC sCustomFormatEtc;

STGMEDIUM sCustomStgMedium;

};


Next, we need to register the custom format and initialize the member variables to valid values, which you do in the CATLControlWin constructor, as shown in Listing 32.44. When you register the custom data format, you are actually registering the format with Windows. That way, whenever an application needs to use the format, that application will get the same ID for the format as the application that registered the format in the first place. All applications that need to use a particular custom format must retrieve the format's ID before using it.

Listing 32.44 ATLCONTROLWIN.H?Registering the Custom Format and Initializing the Member Variables in the Class Constructor

. . .

// set to the first element

ulFORMATETCElement = 0;

// clear the storage medium

sTextStgMedium.hGlobal = NULL;

// register a custom clipboard format

m_uiCustomFormat = ::RegisterClipboardFormat("BCFControlCtlCustomFormat");

// clear the storage medium

sCustomStgMedium.hGlobal = NULL;

}

Transferring the Custom Format

Now that the format is registered, the PrepareDataForTransfer function needs to support the custom data format. In addition to the existing support for the CF_TEXT format, we need to generate the outgoing data in our custom format. When sending data to the Clipboard or a drag target, applications that support multiple formats should render the outgoing data into each format, enabling the receiving application to get the richest version of the data?a drag target that understands only CF_TEXT can get the caption text, but a target that understands the custom format can get it instead. The code to add to PrepareDataForTransfer is shown in Listing 32.45

Listing 32.45 ATLCONTROLWIN.CPP?PrepareDataForTransfer Update

void CATLControlWin::PrepareDataForTransfer(void)

{

. . .

// 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);

// copy all of the members

sCustomFormatEtc.cfFormat = m_uiCustomFormat;

sCustomFormatEtc.ptd = NULL;

sCustomFormatEtc.dwAspect = 0;

sCustomFormatEtc.lindex = -1;

sCustomFormatEtc.tymed = TYMED_HGLOBAL;

// if we have already allocated the data

if(sCustomStgMedium.hGlobal)

// release it

::ReleaseStgMedium(&sCustomStgMedium);

sCustomStgMedium.tymed = TYMED_HGLOBAL;

sCustomStgMedium.hGlobal = hGlobal;

sCustomStgMedium.pUnkForRelease = NULL;

}

}


Listing 32.45 provides half of the needed support, but we still have to update GetDataFromTransfer to accept the custom format on incoming transfers. As with the PrepareDataForTransfer method, we support both the basic text format and our custom format independent of one another. Note that the while loop in this implementation is slightly diferent from the earlier version; the change enables us to examine all of the formats returned by the enumerator and get any data that we can use, instead of stopping at the first usable format. This means that we can get several types of data instead of being locked into whichever one occurs first. The updated GetDataFromTransfer is shown in Listing 32.46.

Listing 32.46 ATLCONTROLWIN.CPP?GetDataFromTransfer Update

void CATLControlWin::GetDataFromTransfer(IDataObject * ipDataObj)



IEnumFORMATETC * ipenumFormatetc;

BOOL bFound = FALSE;

// get a FORMATETC enumerator

if(ipDataObj->EnumFormatEtc(DATADIR_GET, &ipenumFormatetc) == S_OK)

{

// reset the enumerator just to be safe

ipenumFormatetc->Reset();

FORMATETC etc;

// while there are formats to enumerate

while(ipenumFormatetc->Next(1, &etc, NULL) == S_OK)

{

// is this a format that we are looking for?

if(etc.cfFormat == CF_TEXT && etc.tymed & TYMED_HGLOBAL)

{

STGMEDIUM sStgMediumData;

// get the data from the stgmedium

if(ipDataObj->GetData(&etc, &sStgMediumData) == S_OK)

{

// get the global data for this format

// and lock down the memory

LPTSTR lpTempBuffer = (LPTSTR)

::GlobalLock(sStgMediumData.hGlobal);

// if we have a string

if(m_lptstrCaption)

{

// delete the existing string

delete [] m_lptstrCaption;

// clear the reference just to be safe

m_lptstrCaption = NULL;

}

// allocate a new string

m_lptstrCaption = new TCHAR[lstrlen(lpTempBuffer) + 1];

// assign the string to our member variable

lstrcpy(m_lptstrCaption, lpTempBuffer);

// unlock the memory

::GlobalUnlock(sStgMediumData.hGlobal);

// release the storage medium

::ReleaseStgMedium(&sStgMediumData);

// indicate success

bFound = TRUE;

}

}

// is this a format that we are looking for?

else if(m_uiCustomFormat && etc.cfFormat == m_uiCustomFormat &&

etc.tymed & TYMED_HGLOBAL)

{

STGMEDIUM sStgMediumData;

// get the data from the stgmedium

if(ipDataObj->GetData(&etc, &sStgMediumData) == S_OK)

{

// get the global data for this format and lock down the [ic:ccc]memory

LONG * lpTempBuffer = 

(LONG *) ::GlobalLock(sStgMediumData.hGlobal);

// get the data

m_lAlignment = *lpTempBuffer;

// unlock the memory

::GlobalUnlock(sStgMediumData.hGlobal);

// release the storage medium

::ReleaseStgMedium(&sStgMediumData);

// indicate success

bFound = TRUE;

}

}

}

// release the enumerator

ipenumFormatetc->Release();

}

// if we found a format

if(bFound == TRUE)

// force the control to repaint itself

this->FireViewChange();

// this->InvalidateControl(); <== MFC Version

}

Providing Data in the Custom Format

Our transfer routines support the sending and receiving of data in our custom format, but no one will know that the format exists until we modify our IEnumFORMATETC::Next enumerator. The version shown in Listing 32.22 only admits to knowing about CF_TEXT. The new version, shown in Listing 32.47, returns CF_TEXT as the first supported format, followed by our custom format (if the control supports it.) The function determines whether or not the control supports a custom format and whether or not the enumerator is at the second format.

Listing 32.47 ATLCONTROLWIN.CPP?IEnumFORMATETC::Next Update

STDMETHODIMP CATLControlWin::Next(ULONG celt, FORMATETC RPC_FAR * rgelt,

ULONG_RPC_FAR * pceltFetched)

{

// if we are at the beginning of the enumeration

if(ulFORMATETCElement == 0 && celt > 0)

{

// copy all of the members

rgelt->cfFormat = CF_TEXT;

rgelt->ptd = NULL;

rgelt->dwAspect = 0;

rgelt->lindex = -1;

rgelt->tymed = TYMED_HGLOBAL;

// if the caller wants to know how many we copied

if(pceltFetched)

*pceltFetched = 1;

// increment the counter

ulFORMATETCElement++;

// return success

return S_OK;

}

else if(m_uiCustomFormat && ulFORMATETCElement == 1 && celt > 0)

{

// copy all of the members

rgelt->cfFormat = m_uiCustomFormat;

rgelt->ptd = NULL;

rgelt->dwAspect = 0;

rgelt->lindex = -1;

rgelt->tymed = TYMED_HGLOBAL;

// if the caller wants to know how many we copied

if(pceltFetched)

*pceltFetched = 1;

// increment the counter

ulFORMATETCElement++;

// return success

return S_OK;

}

else

// return failure

return S_FALSE;

}


The final requirement is actually returning data to a caller. IEnumFORMATETC::GetData has to be modified to return data from our text STGMEDIUM when text is requested and from our custom format's STGMEDIUM when the custom format is requested. The new version is in Listing 32.48. Note that you can still use the CopyStgMedium function; the only difference is which of CATLControlWin's cached STGMEDIUMs you call it with.

Listing 32.48 ATLCONTROLWIN.CPP?IEnumFORMATETC::GetData Update

STDMETHODIMP CATLControlWin::GetData(LPFORMATETC lpFormatEtc, LPSTGMEDIUM lpStgMedium)

{

// if this is a format that we can deal with

if(lpFormatEtc->cfFormat == CF_TEXT && lpFormatEtc->tymed & TYMED_HGLOBAL)

{

// get a copy of the current stgmedium

this->CopyStgMedium(lpStgMedium, &sTextStgMedium, CF_TEXT);

return S_OK;

}

else if(m_uiCustomFormat && lpFormatEtc->cfFormat == m_uiCustomFormat

&& lpFormatEtc->tymed & TYMED_HGLOBAL)

{

// get a copy of the current stgmedium

this->CopyStgMedium(lpStgMedium, &sCustomStgMedium, m_uiCustomFormat);

return S_OK;

}

else

return IDataObjectImpl<CATLControlWin>::GetData(lpFormatEtc, lpStgMedium);

}

That?s all it takes to support custom formats. By taking a ?black box? approach and separating the data transfer routines from the data preparation routines, you can easily add support for other formats without extensive code changes.

ATL Support for Other Advanced ActiveX Features

ATL enables you to take advantage of many of the available OC 96 and ActiveX features. In some cases, support for these features is automatic.

Dual-Interface Controls

ActiveX controls can communicate with other applications in two ways: using COM interfaces, like IDataObject, or by using interfaces derived from IDispatch. ActiveX automation, discussed in detail in Chapter 29, ?Creating ActiveX Controls,? depends on IDispatch. Without ATL, you'd have to manually add support for IDispatch calls, which is not a light undertaking. By default, ATL control implementations support both kinds of interface, so you do not need to do any additional work. If for some reason you want to generate a control with only one type of interface, you can do so in the Attributes tab of the ATL Object Wizard Properties dialog box.

Windowless Activation

ActiveX controls can request that no new window be generated when they're in-place activated. In this case, the container can receive window messages for the control and dispatch them without requiring the control to have its own window. The IOleInPlaceObjectWindowless interface, implemented in the container, provides this capability. If the container doesn?t support windowless activation, the control must be able to create a window for itself. Windowless activation is a request, and there's a guarantee that all containers will grant it.

ATL supports windowless controls out-of-the-box. When generating a new control, use the Miscellaneous tab of the ATL Object Wizard Properties dialog box to clear the Windowed Only check box if you want the control to support windowless activation. The CComControl class contains a member variable, m_bWindowOnly, which, if set to TRUE, instructs the control to use windowless activation if the container supports it. If set to FALSE, the control will always create a window handle and use it.

Unclipped Device Context

Unclipped device context is an MFC-specific optimization and is not yet implemented in ATL.

Flicker-Free Activation

Flicker-free activation requires that the container support the IOleInPlaceSiteEx interface. ATL automatically attempts to find this interface and will use it if the container supports it. Flicker-free Activation requires no implementation on the part of the developer.

Mouse Pointer Notifications when Inactive

If you need to, you can request notification when the mouse pointer moves over your control while it's inactive. These notifications are provided through the IPointerInactive COM interface. To support this interface in your control, you must add the ATL class IPointerInactiveImpl to your class declaration and override the ATL implementations of the GetActivationPolicy, OnInactiveMouseMove, and OnInactiveSetCursor methods.

Optimized Drawing Code

Optimized drawing is fairly simple to implement, as you saw in the ?Enabling Optimized Drawing? section earlier in this chapter. A parameter of the OnDraw method indicates whether or not the control is able to use optimized drawing. In addition, ATL supports aspect-optimized drawing, which is beyond the scope of this book. If you want to implement this feature, please see the OC 96 specification included in the ActiveX SDK.

From Here...

This chapter focused on expanding the basic control implementation that you created in Chapter 31 by adding support for some advanced ActiveX features. As the user and developer market for ActiveX controls becomes more discerning, these features will become increasingly important in separating successful controls from unsuccessful ones. Fortunately, ATL provides solid support for these features and minimizes the amount of effort required for you to implement them.

The next three chapters explore using MFC for some traditional, but necessary, tasks: managing linked lists, performing recursive operations, and generating new classes from scratch. In the meantime, here are some other information sources that you might find useful: