Полезная информация Подробное описание металлочерепица сталекс киев тут.

Chapter 9
Advanced ActiveX Control Development with ATL


Advanced ActiveX Control Development with ATL

This chapter expands upon the information in Chapter 8 about creating a basic ATL 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 drawing, which are the result of the adoption of OLE Control 96 (OC 96) specification.

Properties

Properties

In Chapter 8, you learn how to add the various types of properties to your control implementation. One type of property that has yet to be examined in terms of ATL is 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 the bitmap 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.

Before you can add your asynchronous property, you need to add the property ReadyState, which is used by the container to determine the state that the control is in at any given time relative to the loading of asynchronous properties. You also add the event ReadyStateChange, which is used by the control to notify the container that the ReadyState property of the control has changed.

Adding the ReadyState property is the same as adding any other property, as is described in Chapter 8. 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, set the Property Type to long, the Property Name to ReadyState, uncheck the Put Function check box, and leave the remainder of the settings at their default values (see fig. 9.1). Click OK to confirm the entry, and close the dialog.

Before you proceed, open the ATLControl.idl file, and change the dispid of the ReadyState property to DISPID_READYSTATE since ReadyState is a stock property (see Listing 9.1).

FIG. 9.1
Add the ReadyState property to the control using the ATL Object Wizard for your asynchronous property support.

Listing 9.1 ATLCONTROL.IDL--Change 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);
};

. . .

The implementation of the ReadyState property requires a member variable to store the ReadyState value. Add the m_lReadyState member to the class declaration of the CATLControlWin class (see Listing 9.2).

Listing 9.2 ATLCONTROLWIN.H--Add the m_lReadyState Member to the CATLControlWin Class

. . .
int iCharWidthArray[256];
int iCharacterSpacing, iCharacterHeight;
// for the ReadyState property
long m_lReadyState;
};

Add the initialization of the m_lReadyState member variable to the constructor of the CATLControlWin class (see Listing 9.3).

Listing 9.3 ATLCONTROLWIN.H--Initialize the m_lReadyState Member Variable in the Class Constructor

CATLControlWin()
{
. . .
// set the initial state of the ReadyState property
m_lReadyState = READYSTATE_LOADING;
}

The last step to implement is the get_ReadyState function, which simply returns the current value of the m_lReadyState member variable (see Listing 9.4).

Listing 9.4 ATLCONTROLWIN.CPP--Implement the get_ReadyState Function to Return the Current ReadyState Value

STDMETHODIMP CATLControlWin::get_ReadyState(long * pVal)
{
// set the return value to the value of the member variable
*pVal = m_lReadyState;
return S_OK;
}

The next step is to add support for the ReadyStateChange event. Open the ATLControl.idl file, and add the ReadyStateChange function to the event interface that is added in Chapter 8 (see Listing 9.5).

Listing 9.5 ATLCONTROL.IDL--Add the ReadyStateChange Event to the IDL File

. . .
[
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 support for events is not automatic in ATL, so you must manually rebuild the CPATLControl.h file that was created in Chapter 8 for your connection point support by using the ATL Proxy Generator. To update the file follow these steps:

  1. Compile the IDL file since 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, double-click the Developer Studio Components folder.

  4. After the Components and Controls Gallery dialog is refreshed with data, double-click the ATL Proxy Generator icon.

  5. Click OK to close the Insert the ProxyGen Component dialog.

  6. Click the ... button to display the Open dialog. Select the ATLControl.tlb file, and click Open.

  7. 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.

  8. A Save dialog appears with the file CPATLControl.h in the File name edit box. Click Save to continue. Click Yes to replace the existing CPATLControl.h file.

  9. Click OK in the confirmation dialog that indicates the operation was successful.

  10. Click Close in the ATL Proxy Generator and Components and Controls Gallery dialogs.

The Fire_ReadyStateChange method is now added to the CProxy_DATLControlWin class.

Asynchronous properties are based on URLs and not on the data type of the data to be downloaded, for example, a bitmap or text file. The URL is stored in a string property of the control. For the sample implementation, you add the property called TextDataPath 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, set the Property Type to BSTR, the Property Name to TextDataPath, and leave the remainder of the settings at their default values (see fig. 9.2). Click OK to confirm the entry and close the dialog.

FIG. 9.2
Add the TextDataPath property to the control using the ATL ClassWizard.

Add the dispidTextDataPath constant to the PROPDISPIDS enumeration in the ATLControl.idl file, and update the TextDataPath function to use the constant value (see Listing 9.6).

Listing 9.6 ATLCONTROL.IDL--Add the dispidTextDataPath Enumeration to the IDL File

. . .
typedef enum propdispids
{
dispidAlignment = 2,
dispidCaptionProp = 3,
dispidTextDataPath = 4,
}PROPDISPIDS;
[
object,
uuid(A19F6963-7884-11D0-BEF3-00400538977D),
dual,
helpstring("IATLControlWin Interface"),
pointer_default(unique)
]
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 the member variable, m_bstrTextDataPath (see Listing 9.7). The data for the member is declared as the type CComBSTR, which is a BSTR wrapper class provided with ATL. See the ATL documentation for more information. The use of the CComBSTR data type versus a standard BSTR or LPTSTR is purely an arbitrary decision on your part and is based on your implementation requirements. We used CComBSTR to demonstrate the different implementation styles available to you with ATL. You also add the member variable m_bstrText, also of the type CComBSTR, to store the data as it is supplied to the control and the member function OnData, which will be the callback function that receives the data as it is downloaded. We will discuss these two members a little later in this chapter.

Listing 9.7 ATLCONTROLWIN.H--The m_bstrTextDataPath Member Variable Is Added to the Class Declaration to Store the TextDataPath Property

. . .
//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;
};
#endif //__ATLCONTROLWIN_H_

The implementation of the get_TextDataPath/put_TextDataPath function is where the asynchronous data transfer of the property takes place (see Listing 9.8). The get_TextDataPath function returns the current value stored in the m_bstrTextDataPath member variable. The put_TextDataPath function stores the new 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. CBindStatusCallback handles all of the details of the data transfer and only requires that you implement a function, in this case OnData, to receive the data as it is downloaded. The OnData function is supplied as the second parameter to the Download function and must conform to the prototype defined by ATL. See the ATL documentation on the Download function for more information.

Listing 9.8 ATLCONTROLWIN.CPP--Implementation of the get_TextDataPath /put_TextDataPath Functions

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 is a very basic implementation of the asynchronous data transfer mechanism provided by the IBindStatusCallback interface and the CBindStatusCallback class (see Listing 9.9). If the OnUData function is called, the new data is appended to the m_bstrText member variable, and the CaptionMethod is called. 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. The BaseCtl sample in Chapters 10 and 11 demonstrates how to implement asynchronous properties using the OnDataAvailable function. OnDataAvailable gives you more information about the download of the data and how it is taking place.

Listing 9.9 ATLCONTROLWIN.CPP--OnData Function Implementation

//OnData will be used as a callback functin 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();
}
}

The final touch of your asynchronous property implementation is to add the property to the persistence macro in your class declaration (see Listing 9.10). Do not add the ReadyState property to the persistence since its value is not valid across execution lifetimes.

Listing 9.10 ATLCONTROLWIN.H--TextDataPath Member Added to the Property Persistence Macro

. . .
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 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. A language-based property 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.

As is pointed out in Chapter 7, property enumeration adds a new level of sophistication to your control with very little effort.

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

Static Property Enumeration
Static property enumeration for an ATL-implemented control is no different than the MFC implementation. Static enumeration is dependent on the ODL/IDL file and involves no control code to implement. See Chapter 7 for more information.

Dynamic Property Enumeration As with your MFC implementation, adding dynamic property enumeration to your ATL implementation is straightforward. Dynamic property enumeration is based on the interface IPerPropertyBrowsing. The ATL Object Wizard does not add this interface to the control class automatically; you are required to add it yourself. The first step is to add the IPerPropertyBrowsingImpl class to your control inheritance structure:

public IPerPropertyBrowsingImpl<CATLControlWin>

And then add the IPerPropertyBrowsing interface to the COM interface map:

COM_INTERFACE_ENTRY_IMPL(IPerPropertyBrowsing)

The last step is to implement the IPerPropertyBrowsing interface functions. First you add the function prototypes to the class declaration (see Listing 9.11).

Listing 9.11 ATLCONTROLWIN.H--IPerPropertyBrowsing Interface Function Prototypes Must Be Added to the Class Declaration

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

MapPropertyToPage (see Listing 9.12) is used to identify a property to a specific control or system-defined property page. In this case, you return E_NOTIMPL if the 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 9.12 ATLCONTROLWIN.CPP--MapPropertyToPage Implementation

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

GetPredefinedStrings is the first function to be called (see Listing 9.13). 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 and it is determined that the correct property is in context, the control is required to create an array of strings and cookies. A cookie is any 32-bit value that has meaning to the control implementation. In this case, the cookies that you supply are the enumeration constants that you added in Chapter 8. The strings are placed in a list from which the user of the control can select the appropriate value to set the property.

Listing 9.13 ATLCONTROLWIN.CPP--GetPredefinedStrings Implementation

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

GetPredefinedValue (see Listing 9.14) is the method that is called when the property browser of the container needs the value associated with the particular dispid and cookie. The value returned is the actual value that is stored in the property and not the string that was used to represent it.

Listing 9.14 ATLCONTROLWIN.CPP--GetPredefinedValue Implementation

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

After the property is set with its value, the property browser calls the function GetDisplayString to get the string that is associated with the current property setting (see Listing 9.15).

Listing 9.15 ATLCONTROLWIN.CPP--GetDisplayString Implementation


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

NOTE: Having the method GetDisplayString when the property browser already has the string for the value from the GetPredefinedStrings function may seem a little redundant. You do this because the GetDisplayString function can be implemented without implementing the other methods. Providing only the function GetDisplayString is done for those property types that do not use the standard property selection mechanism, for example, font selection that uses a font selection dialog and not a list of choices.

Drawing the Control

Optimized drawing allows you to create drawing objects, such as pens or brushes. Rather than removing them when you are finished drawing, you can store them as control member variables and use them 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. Here's 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 is 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 it is available.

For ATL (like 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. For more information on aspect drawing, please see the OC 96 specification that ships with the ActiveX SDK.

Optimized Drawing

In Chapter 8, you learn how to implement standard drawing. In this chapter, you enhance the original implementation to take advantage of drawing optimization.

Add a message handler, OnDestroy, for the Windows message WM_DESTROY. Also add an OnDestroy function prototype to the class declaration (see Listing 9.16). OnDestroy is used to clean up the drawing resources if any are still around when the control is destroyed. This should happen only if the container supports optimized drawing.

Listing 9.16 ATLCONTROLWIN.H--Drawing Implementation Member Variables and Functions

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 next step is to add the OnDestroy implementation, which will clean up the resources if they are still allocated (see Listing 9.17).

Listing 9.17 ATLCONTROLWIN.CPP--OnDestroy Implementation of Drawing Resource Cleanup

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

The last step is to update the OnDraw implementation to take advantage of optimized drawing, if the container supports optimized drawing that is (see Listing 9.18). The only line that you need to add to the code is one that checks the bOptimize member of the ATL_DRAWINFO structure to see if it is set to 0 (or FALSE), which indicates that the container does not support optimized drawing. If that is the case, you then 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 9.18 ATLCONTROLWIN.CPP--OnDraw Function Updated to Support Optimized Drawing

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

Adding Clipboard and Drag and Drop Support

The basic OLE Clipboard and Drag and Drop interfaces are only partially implemented within your control implementation. As for the IPerPropertyBrowsing interface, you must implement the remaining required interfaces yourself. As is pointed out in Chapter 7, Clipboard and Drag and Drop support can add much to your control implementation while requiring very little work.

Clipboard Support

Clipboard support is based on the IDataObject and IEnumFORMATETC interfaces. IDataObject is the interface through which the data is retrieved from your control when it has been placed on the Clipboard, and IEnumFORMATETC is the interface that is used by an application to determine what types of data your 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. Fortunately, the Windows operating system (OS) already has a number of standards in this area. You use Ctrl+X and Shift+Delete for Cut, Ctrl+C and Ctrl+Insert for Copy, and Ctrl+V and Shift+Insert for Paste.

To trap the keystroke combinations, you need to implement a message handler for the WM_KEYDOWN message in the form of a method called OnKeyDown (see Listing 9.19).

Listing 9.19 ATLCONTROLWIN.H--WM_KEYDOWN and OnKeyDown Message Handler Added to the Class Declaration of the Control

. . .
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 implementation looks for the particular keystroke combinations listed in the preceding paragraph and upon finding them invokes the proper set of functions to complete the requested Clipboard operation (see Listing 9.20). Note that in addition to copying the data to the Clipboard, the Cut operation clears the control's data.

Listing 9.20 ATLCONTROLWIN.CPP--OnKeyDown Implementation

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();
// this->InvalidateControl(); <== MFC Version
// we don't need to pass this key to the base implementation
bHandled = TRUE;
}
break;
}
return TRUE;
}

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

CopyDataToClipboard will, as the name implies, get the data from the control and, using the helper function, PrepareDataForTransfer, 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.

When you enable the control for Drag and Drop support, you are aided by the fact that the data transfer functions are separated into two separate methods for each type of transfer, to and from the Clipboard, and then each type of transfer is further broken into two separate steps. Because the basic data transfer mechanism is the same between the Clipboard and Drag and Drop, you are able to rely on a large portion of shared code for each implementation.

Using Built-In Clipboard Formats
As we point out in Chapter 7, the Windows OS supports a number of built-in formats for transferring data via the Clipboard. Your first implementation is to transfer the caption of your control by using the CF_TEXT format, which is the built-in format for transferring ANSI text. 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 applications capability to copy data from the Clipboard. You first look at enabling your control as a Clipboard source.

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. When copying data to the Clipboard, the OnKeyDown implementation takes advantage of several helper functions: CopyDataToClipboard, PrepareDataForTransfer, and CopyStgMedium. CopyStgMedium, CopyDataToClipboard, and PrepareDataForTransfer are used to prepare your data structures--the member variables sTextFormatEtc and sTextStgMedium--for a potential paste operation. The member variable ulFORMATETCElement is the internal counter for the IEnumFORMATETC interface. First you need to add the two function prototypes to the CATLControlWin class declaration (see Listing 9.21).

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

When the user initiates a data transfer via the Clipboard, a reference to the control's IDataObject interface is placed on the Clipboard. At the time the interface is placed on the Clipboard, you have to take a snapshot of the data that the control contains and place it in a STGMEDIUM object. You do this because the data may not be copied from the Clipboard immediately; the data needs to reflect the state of the control when the copy operation was initiated rather than when the paste operation takes place. Once the IDataObject interface is on the Clipboard, you simply wait until someone requests the data. If a supported data format is requested, you copy the data from your STGMEDIUM structure to the STGMEDIUM structure that was passed to you.

The IDataObject interface is already included in your control's inheritance hierarchy. The IEnumFORMATETC interface, however, must be added (see Listing 9.22). Since you will be implementing various member functions of the IDataObject and the IEnumFORMATETC interfaces, you must add the function prototypes to the class declaration of your control. Note that you need to add prototypes for only those functions that you intend to implement; the remainder are left to their default implementations.

Listing 9.22 ATLCONTROLWIN.H--IEnumFORMATETC Interface Added to theClass Inheritance Hierarchy

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

The two member variables, 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 9.23).

Listing 9.23 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;
}
. . .

The next step is to implement the member functions that will actually perform the data preparation and copy operation to the Clipboard.

CopyDataToClipboard is the function that is called to initiate the Clipboard transfer (see Listing 9.24). You first check to see whether you are already the owner of the Clipboard and set the Boolean variable accordingly. You then prepare your data for the Clipboard, and finally if you are not the owner of the Clipboard, you flush whatever data is on the Clipboard and set your IDataObject reference on the Clipboard.

Listing 9.24 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 is the function that you call when you want to copy the data from your control to the STGMEDIUM structure that will represent your data on the Clipboard (see Listing 9.25). First you need to allocate a block of global memory that will contain your caption in ANSI format. Then you set up the FORMATETC and STGMEDIUM structures to reflect the correct data type and exit the function.

Listing 9.25 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);
// copy 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;
}

CopyStgMedium is a general purpose helper function for copying one STGMEDIUM to another STGMEDIUM (see Listing 9.26). This implementation is important because the function allocates a new global data object instead of copying the reference to the existing object.

Listing 9.26 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);
}

The last step is to implement the IDataObject and IEnumFORMATETC interfaces.

The IDataObject implementation is straightforward and involves only two functions (see Listing 9.27). The remainder of the IDataObject 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. Doing this is very important since the ATL class CComControlBase does implement the IDataObject::GetData function for copying metafile 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 implementation functions correctly; however, in this case, the default ATL implementation of this function returns E_NOTIMPL.

Listing 9.27 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);
}
. . .

The IEnumFORMATETC implementation is also simple (see Listing 9.28). The one thing to note is that there is no default ATL implementation, so it is not necessary to defer to the base class implementation in the case where you do not handle the function. Next is used to retrieve the next element in the enumeration; in this case, you need be concerned with only one element, CF_TEXT. After filling in the FORMATETC structure with the appropriate data, you must increment the counter and exit the function. The Skip method increments the counter and exits the function, and Reset sets the counter back to 0.

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

Now that you know how to copy data to the Clipboard, you look at how to get data from the Clipboard.

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 CATLControlWin class declaration to include two new helper functions (see Listing 9.29).

Listing 9.29 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 easy as putting the data on the Clipboard in the first place. The first method is GetDataFromClipboard, which, as the name implies, gets the data from the Clipboard and transfers it to the control. The function first checks the Clipboard to see whether the control already owns the Clipboard. If the control does own the Clipboard, it refreshes the control's data with the data that is stored in the STGMEDIUM structure. The implementation is written this way because the data stored in the control may have changed since it was pasted to the Clipboard in the first place.

If you don't already own the Clipboard, you get the IDataObject reference of the object that does, and you pass the reference on to the GetDataFromTransfer function (see Listing 9.30).

Listing 9.30 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 requests the IEnumFORMATETC interface from the IDataObject and cycles through all of the supported formats looking for one that matches CF_TEXT (see Listing 9.31). Upon finding the appropriate format, it requests the data from the IDataObject supplying a FORMATETC and a STGMEDIUM structure. The data is transferred to the control, and the STGMEDUIM is released. Finally you release the interface pointers and, if you found a format, force the control to repaint itself reflecting the new state of the control.

Listing 9.31 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();
// this->InvalidateControl(); <== MFC Version
}

Remember that earlier in the section, the OnKeyDown function was implemented to support transferring data to the Clipboard. Now you must add the code to support transferring from the Clipboard (see Listing 9.32).

Listing 9.32 ATLCONTROLWIN.CPP--OnKeyDown Implementation

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)
{
// 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();
// this->InvalidateControl(); <== MFC Version
// 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();
// this->InvalidateControl(); <== MFC Version
// 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();
// this->InvalidateControl(); <== MFC Version
// we don't need to pass this key to the base implementation
bHandled = TRUE;
}
break;
}
return TRUE;
}

Custom Clipboard Formats The Clipboard is capable of supporting 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 learn how to support Drag and Drop and then how to support custom formats.

Drag and Drop Support

The fundamentals of Drag and Drop are very similar to Clipboard support and rely on the same set of interfaces for the actual data transfer as well as for two new interfaces: IDropSource and IDropTarget. IDropSource is for those controls that can create data that can be dropped onto another application. IDropTarget is for those controls that can accept data that has been dropped from another application.

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. As with Clipboard transfers, there are two sides to the Drag and Drop coin. An application can be either a Drag and Drop source or a Drag and Drop target, 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 and Drop source. To be a Drag and Drop source, the control must implement the IDropSource interface in addition to the IDataObject and IEnumFORMATETC interfaces. The IDropSource interface is declared in the same manner as the other COM interfaces (see Listing 9.33). The implementation is easy since the interface consists of only two methods.

Listing 9.33 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
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);
// IDropSource
STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD dwKeyState);
STDMETHOD(GiveFeedback)(DWORD dwEffect);
. . .

The first step is to initiate a Drag and Drop operation. You use the left mouse button down event, which is implemented with the WM_LBUTTONDOWN message, to initiate the Drag and Drop operation. You need to implement a message handler for the WM_LBUTTONDOWN message (see Listing 9.34) in the form of a method called OnLButtonDown.

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

. . .
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)
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 is similar to the Clipboard method CopyDataToClipboard (see Listing 9.35) in that it prepares the data for the transfer and sets a reference to the data. See Chapter 7 and the Win32 documentation for more information regarding the use of the function DoDragDrop and the drop effect constants.

Listing 9.35 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 last step is the IDropSource interface implementation (see Listing 9.36).

QueryContinueDrag is used to instruct OLE as to how the Drag and Drop operation should be handled at the time that the state of the keyboard or mouse changes. This is usually indicative of a drop operation. In your case, you are looking to see whether the left mouse button is no longer being held down. If that is the case, the drop operation is completed. Otherwise, you just exit the method.

GiveFeedback is used to instruct OLE as to what cursors should be used while performing the Drag and Drop operation. In this case, you use the default cursors.

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

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

Enabling a Control as a Drag and 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. As with your Drag and Drop source support, you build upon the interfaces you've already created and add some new functionality. Again, you add the IDropTarget interface in the same fashion as the other interfaces (see Listing 9.37).

Listing 9.37 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);
. . .

In order for the control to be a Drag and Drop target, you must do more than just support the appropriate ActiveX interfaces. You must register the control as a drop target with the Windows OS. You must call RegisterDragDrop to register the control as a valid drop target. Before you can add the RegisterDragDrop call, you must add a WM_CREATE message handler to the CATLControlWin class declaration (see Listing 9.38).

Listing 9.38 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 implementation calls the RegisterDragDrop function passing the window handle of the control (see Listing 9.39).

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

When the control is being destroyed, you need to call RevokeDragDrop to remove the control as a valid drop target, which you will do in the OnDestroy function (see Listing 9.40), which was added earlier in this chapter.

Listing 9.40 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);
. . .

Finally you implement the IDropTarget interface (see Listing 9.41).

DragEnter is where you instruct OLE as to whether the current drag operation that has entered the control is valid for the control's implementation. You first look for the appropriate mouse or keyboard state, which, in your case, is the left mouse button being held down. Next you use the IEnumFORMATETC interface to see whether the IDataObject that was passed to you contains any formats that you can use.

DragOver is used to instruct windows as to the current state of the drag operation while it is over the control. This implementation is very basic. One could, however, implement the method to allow the Drag and Drop operation over only specific portions of the control by checking the point structure that was passed in and comparing it to various locations of the control. For example, a grid control might allow only text data to be dropped on the headings, but both text and numeric data while over the columns.

DragLeave is used to clean up any state information that may have been created locally to the control when the DragEnter was invoked. In your case, you return E_NOTIMPL since you have no use for the function.

Drop is the last function that you need to implement and is where you copy the data from the IDataObject to the control using the GetDataFromTransfer method.

Listing 9.41 ATLCONTROLWIN.CPP--IDropTarget Interface Implementation


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;
}
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;
}
STDMETHODIMP CATLControlWin::DragLeave(void)
{
return E_NOTIMPL;
}
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;
}

As with your MFC (and later BaseCtl), implementations adding Drag and Drop support are straightforward. Now that you have addressed the built-in data transfer formats, you can take a look at the next step, custom data formats.

Custom Clipboard and Drag and Drop Formats

A custom data format is one that is understood by the exchanging applications but does not fall into the category of predefined formats. For your implementation, you transfer the text Alignment property along with the Caption. You are not restricted in any way in the types of data that can be transferred in this manner.

Adding custom data formats is independent of the mechanism used to initiate the data transfer. Since you have modeled your data transfer methods based on this principle, you need to make only one set of changes to your application to accommodate both custom Clipboard and Drag and Drop operations.

The first step is adding the member variables that you will use to implement the custom format (see Listing 9.42). The member variable m_uiCustomFormat will be used to hold the ID number of the registered custom format. The remaining members are used to hold the data and its related formatting information.

Listing 9.42 ATLCONTROLWIN.H--Custom Data Format Member Variables

. . .
private:
FORMATETC sTextFormatEtc;
STGMEDIUM sTextStgMedium;
// custom format storage variables
UINT m_uiCustomFormat;
FORMATETC sCustomFormatEtc;
STGMEDIUM sCustomStgMedium;
};

The next step is to register the custom format and initialize the member variables to valid values, which you do in the constructor (see Listing 9.43). When you register the custom data format, you are actually registering the format in the Windows OS. That way, whenever an application needs to use the format, that application will get the same value as the application that registered the format in the first place. All applications that need to use a custom format must call this method to retrieve the ID associated with the custom format type.

Listing 9.43 ATLCONTROLWIN.H--Register the Custom Format and Initialize 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;
}

Next you update the PrepareDataForTransfer function to support the custom data format (see Listing 9.44). In addition to the CF_TEXT format, you add the creation of the custom data format, if there is one. You store the new format in the custom storage variables so that you can support the formats on a granular basis. If the application receiving the data understands only the text format, that is all that the application needs to retrieve; but if the application understands both, the application can get both.

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

Next you update the GetDataFromTransfer method (see Listing 9.45), which you will use to copy the data from a SGTMEDUIM structure to the control. As with the PrepareDataForTransfer method, you take a granular approach and support the basic text transfer independent of the custom format. Note that you change the while loop slightly so that you can look through all of the available formats and stop only when you have looked at all of them. This way, you can get the text format and the custom format independent of each other, thus preventing them from being mutually exclusive.

Listing 9.45 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 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
}

Now that you have updated the basic data transfer routines, you need to update the IEnumFORMATETC interface to publish the availability of the new format to any application that wants it. You do this in the IEnumFORMATETC::Next function (see Listing 9.46). The function looks to see whether the control supports a custom format and whether the enumerator is at the second format. The function will then fill in the FORMATETC structure that was passed in with the appropriate information, letting any application that understands the custom format know that the control can support the custom format, too.

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

Last you need to update the routine that returns the custom format in the STGMEDIUM structure, IEnumFORMATETC::GetData (see Listing 9.47). You can still use the CopyStgMedium function; the only difference is which internal STGMEDIUM structure is supplied to the function.

Listing 9.47 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 to how you've created the basic data transfer routines, you can support a large amount of functionality, while relying on a relatively small common code base.

Subclassing Existing Windows Controls

As with your MFC implementation, ATL allows for the creation of controls that subclass existing Windows controls. At the beginning of this chapter, you created several controls in the application, one of which subclassed a Windows BUTTON control. The CATLControlSubWin class varies little from the CATLControlWin class. All of the differences are contained within the ATLControlSubWin.h header file (see Listing 9.48).

ATL relies on the class CContainedWindow to implement its subclassing feature. The constructor of the primary control is where the constructor of the contained class is supplied with the name of the Windows control to use when performing the actual subclassing routine. The ATL Object Wizard also adds a message handler for the WM_CREATE message that instructs the contained window to create itself.

The function SetObjectRects is overloaded so that the contained control will size itself correctly in tandem with the container control.

Listing 9.48 ATLCONTROLSUBWIN.H--CATLControlSubWin Class Implementation

/////////////////////////////////////////////////////////////////////////////
// CATLControlSubWin
class ATL_NO_VTABLE CATLControlSubWin :
public CComObjectRootEx<CComObjectThreadModel>,
public CComCoClass<CATLControlSubWin, &CLSID_ATLControlSubWin>,
public CComControl<CATLControlSubWin>,
public IDispatchImpl<IATLControlSubWin, &IID_IATLControlSubWin,
&LIBID_ATLCONTROLLib>,
public IProvideClassInfo2Impl<&CLSID_ATLControlSubWin, NULL, &LIBID_ATLCONTROLLib>,
public IPersistStreamInitImpl<CATLControlSubWin>,
public IPersistStorageImpl<CATLControlSubWin>,
public IQuickActivateImpl<CATLControlSubWin>,
public IOleControlImpl<CATLControlSubWin>,
public IOleObjectImpl<CATLControlSubWin>,
public IOleInPlaceActiveObjectImpl<CATLControlSubWin>,
public IViewObjectExImpl<CATLControlSubWin>,
public IOleInPlaceObjectWindowlessImpl<CATLControlSubWin>,
public IDataObjectImpl<CATLControlSubWin>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<CATLControlSubWin>,
public ISpecifyPropertyPagesImpl<CATLControlSubWin>
{
public:
CContainedWindow m_ctlButton;

CATLControlSubWin() :
m_ctlButton(_T("Button"), this, 1)
{

m_bWindowOnly = TRUE;
}
DECLARE_REGISTRY_RESOURCEID(IDR_ATLCONTROLSUBWIN)
BEGIN_COM_MAP(CATLControlSubWin)
COM_INTERFACE_ENTRY(IATLControlSubWin)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_IMPL(IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject2, IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject, IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL(IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleInPlaceObject, IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleWindow, IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL(IOleInPlaceActiveObject)
COM_INTERFACE_ENTRY_IMPL(IOleControl)
COM_INTERFACE_ENTRY_IMPL(IOleObject)
COM_INTERFACE_ENTRY_IMPL(IQuickActivate)
COM_INTERFACE_ENTRY_IMPL(IPersistStorage)
COM_INTERFACE_ENTRY_IMPL(IPersistStreamInit)
COM_INTERFACE_ENTRY_IMPL(ISpecifyPropertyPages)
COM_INTERFACE_ENTRY_IMPL(IDataObject)
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
END_COM_MAP()
BEGIN_PROPERTY_MAP(CATLControlSubWin)
// PROP_ENTRY("Description", dispid, clsid)
PROP_PAGE(CLSID_CColorPropPage)
END_PROPERTY_MAP()
BEGIN_CONNECTION_POINT_MAP(CATLControlSubWin)
END_CONNECTION_POINT_MAP()
BEGIN_MSG_MAP(CATLControlSubWin)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
ALT_MSG_MAP(1)
// Replace this with message map entries for subclassed Button
END_MSG_MAP()
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT rc;
GetWindowRect(&rc);
rc.right -= rc.left;
rc.bottom -= rc.top;
rc.top = rc.left = 0;
m_ctlButton.Create(m_hWnd, rc);
return 0;
}
STDMETHOD(SetObjectRects)(LPCRECT prcPos,LPCRECT prcClip)
{
IOleInPlaceObjectWindowlessImpl<CATLControlSubWin>::SetObjectRects(
prcPos, prcClip);
int cx, cy;
cx = prcPos->right - prcPos->left;
cy = prcPos->bottom - prcPos->top;
::SetWindowPos(m_ctlButton.m_hWnd, NULL, 0,
0, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE);
return S_OK;
}
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
// IViewObjectEx
STDMETHOD(GetViewStatus)(DWORD* pdwStatus)
{
ATLTRACE(_T("IViewObjectExImpl::GetViewStatus\n"));
*pdwStatus = VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE;
return S_OK;
}
// IATLControlSubWin
public:
HRESULT OnDraw(ATL_DRAWINFO& di);
};

The ATL Sample Index provides access to several sample applications that subclass Windows controls. Because subclassing is straightforward and easy to implement, there is not much to the samples.

Subclassing is an easy way to add specialized controls to your application. Providing list boxes or buttons that perform unique actions relative to your implementation can save you a lot of development time, especially if there is use for the controls in more than one instance. Subclassing involves very little work and provides a lot of benefit.

Dual-Interface Controls

ATL control implementations, by default, support dual-interface, so no extra work is needed. As we stated in previous chapters, however, currently no control containers can or will use dual-interfaces within controls.

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



Other ActiveX Features

As with your MFC implementation, ATL allows you to take advantage of some of the available OC 96 or ActiveX features. Chapter 6 contains a detailed explanation of each feature, so we don't go into them here. We do, however, look into the aspects of their specific implementation and how they relate to ATL.

Windowless Activation

Windowless activation is supported through the IOleInPlaceObjectWindowless interface and is implemented in the container. If the container doesn't support windowless activation, the control must be able to create a window for itself. Windowless activation is a request not a guarantee.

ATL support of windowless controls is easy to implement. The CComControl class contains a member variable m_bWindowOnly, which, if set to TRUE, instructs the control to use its windowless feature if the control supports it. If set to FALSE, the control will always create a window handle. The class CATLControlNoWin, which is the class of the control you created at the beginning of the chapter to support windowless activation, sets the m_bWindowOnly member to TRUE within its constructor. A number of member variables and functions are related to windowless control creation; so you need to review the ATL documentation thoroughly.

Flicker-Free Activation

Flicker-free activation is based on the IOleInPlaceSiteEx interface. The ATL framework automatically attempts to find this interface, so flicker-free activation requires no implementation on the part of the developer.

Mouse Pointer Notifications When Inactive

Mouse pointer notifications when inactive are provided through the COM interface IPointerInactive. To support the 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.

Optimized Drawing Code

Optimized drawing is handled much as in MFC, as you saw in the optimized drawing section earlier in this chapter. A parameter of the OnDraw method indicates whether the control can draw using the optimized techniques you first saw in your MFC implementation. In addition, the ATL implementation allows for aspect or optimized drawing. Drawing with aspects 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.

Loads Properties Asynchronously

To support asynchronous properties, a control must support the stock property ReadyState and the stock event ReadyStateChange. The control is responsible for updating the property and notifying the container when it has changed. Other than the OnData function, which is already provided by the ATL framework, the implementation must be performed manually by the developer of the control, as you saw in the asynchronous property section earlier in this chapter.

From Here...

This chapter focused on expanding the basic control implementation that you created in Chapter 8. The advanced features and functionality that you learned, such as asynchronous properties, Drag and Drop support, and many others, will allow you to distinguish your control implementation as being a truly professional implementation.

With the introduction of version 2.1, ATL has come full circle and provides you with a complete and robust framework for developing ActiveX controls and ActiveX components in general. The amount of support being put into this product both by the industry and Microsoft should tell you that ATL is the way to do ActiveX development in the future. Chapters 10 and 11 examine in detail how to create a similar control implementation using BaseCtl.