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