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.
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.
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.
. . .
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).
. . .
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).
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).
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).
. . .
[
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:
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).
. . .
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.
. . .
//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.
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.
//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.
. . .
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()
. . .
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
Dynamic Property Enumeration
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).
. . .
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.
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.
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.
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).
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.
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.
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.
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).
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.
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;
}
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 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).
. . .
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.
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. . . . 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.
. . . 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).
. . . 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.
void CATLControlWin::CopyDataToClipboard(void) 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.
void CATLControlWin::PrepareDataForTransfer(void) 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.
void CATLControlWin::CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,
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.
. . . 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.
STDMETHODIMP CATLControlWin::Next(ULONG celt, FORMATETC_RPC_FAR
* rgelt, Now that you know how to copy data to the Clipboard, you look at how to get data
from the Clipboard. . . . 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).
void CATLControlWin::GetDataFromClipboard(void) 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.
void CATLControlWin::GetDataFromTransfer(IDataObject
* ipDataObj) 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).
LRESULT CATLControlWin::OnKeyDown(UINT uMsg, WPARAM
wParam, LPARAM lParam, Custom Clipboard Formats 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. . . . 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.
. . . 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.
LRESULT CATLControlWin::OnLButtonDown(UINT uMsg, WPARAM
wParam, LPARAM lParam, 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.
STDMETHODIMP CATLControlWin::QueryContinueDrag(BOOL fEscapePressed,
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. . . . 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).
. . . The OnCreate implementation calls the RegisterDragDrop function
passing the window handle of the control (see Listing 9.39).
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.
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.
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.
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.
. . . 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.
. . . 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.
void CATLControlWin::PrepareDataForTransfer(void) 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.
void CATLControlWin::GetDataFromTransfer(IDataObject
* ipDataObj) 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.
STDMETHODIMP CATLControlWin::Next(ULONG celt, FORMATETC
RPC_FAR * rgelt, 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.
STDMETHODIMP CATLControlWin::GetData(LPFORMATETC 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.
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.
/////////////////////////////////////////////////////////////////////////////
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.
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: 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 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 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 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 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.
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.
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.
© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a
Simon and Schuster Company.
Using Built-In Clipboard Formats
Enabling a Control as a Clipboard Source Listing 9.21
void CopyDataToClipboard(void);
void PrepareDataForTransfer(void);
void CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,
LPSTGMEDIUM lpSourceStgMedium, CLIPFORMAT cfSourceFormat);
ULONG ulFORMATETCElement;
private:
FORMATETC sTextFormatEtc;
STGMEDIUM sTextStgMedium;
}; Listing 9.22
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);
. . . Listing 9.23
// 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;
}
. . . Listing 9.24
{
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)));
}
} Listing 9.25
{
// 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;
} Listing 9.26
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);
} Listing 9.27
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);
}
. . . Listing 9.28
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;
}
Enabling a Control as a Clipboard Target Listing 9.29
void CopyDataToClipboard(void);
void PrepareDataForTransfer(void);
void GetDataFromClipboard(void);
void GetDataFromTransfer(IDataObject * ipDataObj);
void CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,
LPSTGMEDIUM lpSourceStgMedium, CLIPFORMAT cfSourceFormat);
. . . Listing 9.30
{
// 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();
}
} Listing 9.31
{
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
} Listing 9.32
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;
} Drag and Drop Support
Using Built-In Drag and Drop Formats
Enabling a Control as a Drag and Drop Source Listing 9.33
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);
. . . Listing 9.34
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);
. . . Listing 9.35
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;
} Listing 9.36
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;
}
Enabling a Control as a Drag and Drop Target Listing 9.37
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);
. . . Listing 9.38
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);
. . . Listing 9.39
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;
} Listing 9.40
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);
. . . Listing 9.41
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;
} Custom Clipboard and Drag and Drop Formats
Listing 9.42
private:
FORMATETC sTextFormatEtc;
STGMEDIUM sTextStgMedium;
// custom format storage variables
UINT m_uiCustomFormat;
FORMATETC sCustomFormatEtc;
STGMEDIUM sCustomStgMedium;
}; Listing 9.43
// 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;
} Listing 9.44
{
. . .
// 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 9.45
{
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
} Listing 9.46
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;
} Listing 9.47
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);
} Subclassing Existing Windows Controls
Listing 9.48
// 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);
};Dual-Interface Controls
Unclipped device context is an MFC-specific optimization and is not implemented in
ATL.
Other ActiveX FeaturesWindowless Activation
Flicker-Free Activation
Mouse Pointer Notifications When Inactive
Optimized Drawing Code
Loads Properties Asynchronously
From Here...