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

Chapter 11
Advanced ActiveX Control Development with BaseCtl


Advanced ActiveX Control Development with BaseCtl

This chapter expands upon the information in Chapter 10 about creating a basic BaseCtl ActiveX control, so reading Chapter 10 prior to this chapter is necessary. 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 OC 96 specification.

Creating Properties

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

Creating Asynchronous Properties

Asynchronous properties are those properties that typically represent a large amount of data, such as a text or bitmap file, 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.

To allow for asynchronous property support, you have to modify your class definition slightly. Listing 11.1 shows the changes that were made to your BCFControlControl class header file.

The BaseCtl class COleControl does not provide support for asynchronous properties. You need to take advantage of the BaseCtl class CInternetControl in order to do that.

You need to include the Internet.h file and derive the class BCFControlControl from CInternetControl, which is derived from the base class COleControl. You also add the method OnData as your callback function. The callback function is what OLE uses to notify your control that data is being downloaded and is required for asynchronous property support.

Listing 11.1 BCFCONTROLCTL.H--Modified BCFControlControl Class Definition

. . .
// class declaration for the BCFControl control.
//
#ifndef _BCFCONTROLCONTROL_H_
#include "IPServer.H"
#include "CtrlObj.H"
#include "BCFControlInterfaces.H"
#include "Dispids.H"
#include "internet.h"
#include "alignmentenums.h"
typedef struct tagBCFCONTROLCTLSTATE
{
long lCaptionLength;
long lAlignment;
OLE_COLOR ocBackColor;
} BCFCONTROLCTLSTATE;
//=-------------------------------------------------------------------------=
// CBCFControlControl
//=-------------------------------------------------------------------------=
// our control.
//
class CBCFControlControl : public CInternetControl, public IBCFControl, public ISupportErrorInfo
{
. . .
// OnData is called asynchronously as data for an
// object or property arrives...
virtual HRESULT OnData(DISPID propId, DWORD bscfFlag, IStream * strm,
DWORD dwSize);
// private state information.
//
BCFCONTROLCTLSTATE m_state;
. . .

Listing 11.2 shows your changes to the implementation of your constructor to enable asynchronous property support. Your constructor implementation is trivial since your only change is to replace the COleControl constructor declaration with CInternetControl. Note the pearls of wisdom from the authors of the BaseCtl sample.

Listing 11.2 BCFCONTROLCTL.CPP--BCFControlControl Constructor Implementation Change

//=-------------------------------------------------------------------------=
// CBCFControlControl::CBCFControlControl
//=-------------------------------------------------------------------------=
// "Being born is like being kidnapped. And then sold into slavery."
// - andy warhol (1928 - 87)
//
// Parameters:
// IUnknown * - [in]
//
// Notes:
//
#pragma warning(disable:4355) // using `this' in constructor
CBCFControlControl::CBCFControlControl
(
IUnknown *pUnkOuter
)
: CInternetControl(pUnkOuter, OBJECT_TYPE_CTLBCFCONTROL, (IDispatch *)this)
{
// initialize anything here
...

Listing 11.3 shows the OnData function that you added to your source file. For now, you just add the function shell; you will add the specific implementation after you add your data path property.

Listing 11.3 BCFCONTROLCTL.CPP--OnData Function

...
HRESULT CBCFControlControl::OnData(DISPID propId, DWORD bscfFlag, IStream * strm,
DWORD dwSize)
{
HRESULT hr = NOERROR;
return(hr);
}

NOTE: We experienced some link problems when compiling our BCFControl sample code, as follows: Unresolved external: CreateURLMoniker Unresolved external: RegisterBindStatusCallback Even though the functions are declared in urlmon.h and should be implemented in uuid.lib, we found that we had to link with Urlmon.lib to resolve the functions.



Before you add your specific implementation code to support the asynchronous property, you need to add the stock property ReadyState to your control.

You also need to add a user-defined property for your data path variable. This property is used to store the location of the asynchronous properties data. This location can be any valid pathname, including URL and UNC paths.

First you need to declare a dispid for your data path property (see Listing 11.4). You use the OLE defined dispid for the ReadyState property.

Listing 11.4 DISPIDS.H--Add Data Path Property Dispid

. . .
//=-------------------------------------------------------------------------=
// for the BCFControl control
// properties & methods
//
#define dispidAlignment 1
#define dispidCaptionMethod 2
#define dispidCaptionProp 3
#define dispidTextDataPath 4
. . .

Next you add the two new properties to your ODL file (see Listing 11.5). Note that you add only a method for getting the ReadyState property, and not a method for setting the property, which has the effect of creating a read-only property.

Listing 11.5 BCFCONTROL.ODL--BCFControl ODL Implementation

. . .
[uuid(317512F1-3E75-11d0-BEBE-00400538977D), helpstring("BCFControl Control"),
hidden, dual, odl]
interface IBCFControl : IDispatch
{
// properties
[id(dispidAlignment), propget] HRESULT Alignment([out, retval] long * Value);
[id(dispidAlignment), propput] HRESULT Alignment([in] long Value);
[id(DISPID_BACKCOLOR), propget] HRESULT BackColor([out, retval] OLE_COLOR * Value);
[id(DISPID_BACKCOLOR), propput] HRESULT BackColor([in] OLE_COLOR Value);
[id(DISPID_READYSTATE), propget] HRESULT ReadyState([out, retval] long * Value);
[id(dispidTextDataPath), propget] HRESULT TextDataPath([out, retval] BSTR * Value);
[id(dispidTextDataPath), propput] HRESULT TextDataPath([in] BSTR Value);
// methods . . .

You need to add a member variable to your state structure to store the state of the control's asynchronous properties, and you also add a string length member that will be used later in your persistence routines.

You also need to add your function prototypes of the property get/set methods to your class header file (see Listing 11.6). Remember that the prototype is generated automatically when the ODL file is compiled.

Finally you add a string member to hold the value of the property.

Listing 11.6 BCFCONTROLCTL.H--ReadyState Property Prototype

. . .
typedef struct tagBCFCONTROLCTLSTATE

{

long lCaptionLength;

long lAlignment;

OLE_COLOR ocBackColor;

long lReadyState;

long lTextDataPathLength;

} BCFCONTROLCTLSTATE;
//=-------------------------------------------------------------------------=

// CBCFControlControl

//=-------------------------------------------------------------------------=
. . .
// IBCFControl methods

//

STDMETHOD(get_Alignment)(THIS_ long FAR* Value);

STDMETHOD(put_Alignment)(THIS_ long Value);

STDMETHOD(get_BackColor)(THIS_ OLE_COLOR FAR* Value);

STDMETHOD(put_BackColor)(THIS_ OLE_COLOR Value);

STDMETHOD(get_ReadyState)(THIS_ long FAR* Value);

STDMETHOD(get_TextDataPath)(THIS_ BSTR FAR* bstrRetVal);

STDMETHOD(put_TextDataPath)(THIS_ BSTR Value);

STDMETHOD(CaptionMethod)(THIS_ BSTR bstrCaption, VARIANT varAlignment,

long FAR* lRetVal);

STDMETHOD(get_CaptionProp)(THIS_ VARIANT varAlignment, BSTR FAR* bstrRetVal);

STDMETHOD(put_CaptionProp)(THIS_ VARIANT varAlignment, BSTR lpszNewValue);

STDMETHOD_(void, AboutBox)(THIS);
. . .
// private state information.

//

BCFCONTROLCTLSTATE m_state;
protected:

LPTSTR m_lptstrCaption;

LPTSTR m_lptstrTextDataPath;
};

Listing 11.7 shows your member variable initialization in your constructor. You don't need to set any default values since the ReadyState will not be persisted across execution lifetimes.

Listing 11.7 BCFCONTROLCTL.CPP--Member Initialization

#pragma warning(disable:4355) // using `this' in constructor

CBCFControlControl::CBCFControlControl

(

IUnknown *pUnkOuter

)

: CInternetControl(pUnkOuter, OBJECT_TYPE_CTLBCFCONTROL, (IDispatch *)this)

{

// initialize anything here ...

//
...
// set the ready state of the control

m_state.lReadyState = READYSTATE_LOADING;
// NULL terminate the string reference

m_lptstrTextDataPath = new TCHAR[1];

m_lptstrTextDataPath[0] = `\0';
}
#pragma warning(default:4355) // using `this' in constructor

Last you add your implementation of the ReadyState and TextDataPath methods to your class source file (see Listing 11.8). You initiate the asynchronous download of your data within your put_TextDataPath method. You do this through a call to SetupDownload, where you pass in the path of the data to be downloaded and the dispid of the property that the data is bound to. As data becomes available, your OnData method is called.

Listing 11.8 BCFCONTROLCTL.CPP--Property Implementation

STDMETHODIMP CBCFControlControl::get_ReadyState(long * Value)

{

// make sure that we have a good pointer

CHECK_POINTER(Value);
// set the return value

*Value = m_state.lReadyState;
// return the result

return S_OK;

}
STDMETHODIMP CBCFControlControl::get_TextDataPath(BSTR FAR * bstrRetVal)

{

// if there is a string

if(*bstrRetVal);

{

// free the string because we are going to replace it

::SysFreeString(*bstrRetVal);
// clear the reference just to be safe

*bstrRetVal = NULL;

}
// return the caption as a BSTR

*bstrRetVal = ::SysAllocString(OLESTRFROMANSI(m_lptstrTextDataPath));
return S_OK;

}
STDMETHODIMP CBCFControlControl::put_TextDataPath(BSTR bstrNewValue)

{

HRESULT hResult = S_OK;
// get a ANSI string from the BSTR

MAKE_ANSIPTR_FROMWIDE(lpctstrTextDataPath, bstrNewValue);
// if we have a string

if(lpctstrTextDataPath != NULL)

{

// if we have a string

if(m_lptstrTextDataPath)

{

// delete the existing string

delete [] m_lptstrTextDataPath;
// clear the reference just to be safe

m_lptstrTextDataPath = NULL;

}
// allocate a new string

m_lptstrTextDataPath = new TCHAR[lstrlen(lpctstrTextDataPath) + 1];
// assign the string to our member variable

lstrcpy(m_lptstrTextDataPath, lpctstrTextDataPath);

}
// start the asynchronous download of the data

this->SetupDownload(OLESTRFROMANSI(m_lptstrTextDataPath), dispidTextDataPath);
// let the container know that the property has changed

m_fDirty = TRUE;

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

Listing 11.9 shows your implementation of your OnData method, which will progressively render the caption of your control from the IStream supplied. It is possible to receive the first and last notification messages within a single call to OnData, which is the reason for the separation between the BSCF_FIRSTDATANOTIFICATION and BSCF_LASTDATANOTIFICATION messages. Another flag that can be passed to the function is BSCF_INTERMEDIATEDATANTIFICATION, which indicates that additional data is to be passed and that you have not received it all. Your OnData function assumes that multiple calls to load data will be made and checks for only the first and last notification messages.

Listing 11.9 BCFCONTROLCTL.CPP--OnData Implementation

HRESULT CBCFControlControl::OnData(DISPID propId, DWORD bscfFlag, IStream * strm, DWORD dwSize)

{

HRESULT hr = NOERROR;
// if this is the first notification

if(bscfFlag & BSCF_FIRSTDATANOTIFICATION)

{

// if we have a reference

if(m_lptstrCaption)

{

// delete the string buffer

delete [] m_lptstrCaption;
// clear the reference just to be safe

m_lptstrCaption = NULL;

}

}
// alloc a temp buffer

LPTSTR lptstrTempBuffer = new TCHAR[dwSize + 1];



ULONG ulBytesRead;

// read the data to a temp buffer

hr = strm->Read(lptstrTempBuffer, dwSize, &ulBytesRead);
// if we read in any data

if(hr == S_OK && ulBytesRead)

{

// null terminate the amount of data the was actually read in

lptstrTempBuffer[ulBytesRead] = `\0';
// get a new buffer with enough space to hold all of the data

LPTSTR lptstrNewBuffer = new TCHAR[lstrlen(m_lptstrCaption) + ulBytesRead + 1];
// copy the existing data to the new buffer

lstrcpy(lptstrNewBuffer, m_lptstrCaption);
// add the new data to the buffer

lstrcat(lptstrNewBuffer, lptstrTempBuffer);
// remove the existing string

delete [] m_lptstrCaption;
// copy the data to the buffer

m_lptstrCaption = lptstrNewBuffer;
// set the dirty flag

m_fDirty = TRUE;
// redraw the control

this->InvalidateControl(NULL);

}
// if this is our last notification

if(bscfFlag & BSCF_LASTDATANOTIFICATION)

{

// set the ready state of the control

m_state.lReadyState = READYSTATE_COMPLETE;
// set the dirty flag

m_fDirty = TRUE;

}
// return the result

return(hr);
}

Potentially, any type of data can be rendered in this fashion. The BaseCtl framework provides a sample implementation, called WebImage, that demonstrates the rendering of bitmap data progressively as an asynchronous property.

Static and Dynamic Property Enumeration

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

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

Static Property Enumeration Static Property Enumeration for a BaseCtl implemented control is no different than your MFC and ATL implementations. Static enumeration is dependent on the ODL and requires no control code to implement it. See Chapter 7 for the implementation details.

Dynamic Property Enumeration As with your MFC and ATL implementations, adding Dynamic Property Enumeration to your BaseCtl implementation is straightforward. Unfortunately, the BaseCtl does not provide the basic OLE interface for Dynamic Property Enumeration support that you found in MFC, so you must add it yourself. Dynamic Property Enumeration is based on the interface IPerPropertyBrowsing. You will create a macro in a style similar to that of the BaseCtl that will provide the necessary code to implement the interface. Listing 11.10 shows the macro and the definition that you added. Essentially, the macro is just a collection of functions that need to be supported in order to use a specific interface. You are not required to implement the interface with a macro as your are doing here. The macro just makes your control class code a little bit easier to read and manage.

Listing 11.10 IPERPROPERTYBROWSING.H--IPerPropertyBrowsing Interface Macro

#define DECLARE_STANDARD_PERPROPERTYBROWSING() \

STDMETHOD(MapPropertyToPage)(DISPID Dispid, LPCLSID lpclsid); \

STDMETHOD(GetPredefinedStrings)(DISPID Dispid, CALPOLESTR* lpcaStringsOut,\

CADWORD* lpcaCookiesOut); \

STDMETHOD(GetPredefinedValue)(DISPID Dispid, DWORD dwCookie, VARIANT* lpvarOut); \
STDMETHOD(GetDisplayString)(DISPID Dispid, BSTR* lpbstr); \

In order for your control to support IPerPropertyBrowsing, you need to include the header file for your IPerPropertyBrowsing macro, inherit from the IPerPropertyBrowsing interface, and add the macro to your class declaration (see Listing 11.11).

Listing 11.11 BCFCONTROLCTL.H--IPerPropertyBrowsing Interface Declaration

#include "Dispids.H"

#include "internet.h"

#include "IPerPropertyBrowsing.h"
#include "alignmentenums.h"
typedef struct tagBCFCONTROLCTLSTATE

{

long lCaptionLength;

long lAlignment;

OLE_COLOR ocBackColor;

long lReadyState;

long lTextDataPathLength;

} BCFCONTROLCTLSTATE;
//=-------------------------------------------------------------------------=

// CBCFControlControl

//=-------------------------------------------------------------------------=

// our control.

//

class CBCFControlControl : public CInternetControl, public IBCFControl,

public ISupportErrorInfo, public IPerPropertyBrowsing

{

public:

// IUnknown methods

//

DECLARE_STANDARD_UNKNOWN();
// IDispatch methods

//

DECLARE_STANDARD_DISPATCH();
// ISupportErrorInfo methods

//

DECLARE_STANDARD_SUPPORTERRORINFO();
// IPerPropertyBrowsing methods

//

DECLARE_STANDARD_PERPROPERTYBROWSING();
// IBCFControl methods

//
STDMETHOD(get_Alignment)(THIS_ long FAR* lRetValue);

When an application needs to use an interface in a control (or any component for that matter), the application has to call QueryInterface to locate the correct interface pointer within the component. This requirement is also true for the IPerPropertyBrowsing interface. Listing 11.12 shows the change that you must make to your InternalQueryInterface function in order to support the new interface. This change is required because the control will not function correctly without it.

Listing 11.12 BCFCONTROLCTL.CPP--QueryInterface Implementation of IPerPropertyBrowsing

HRESULT CBCFControlControl::InternalQueryInterface(REFIID riid, void **ppvObjOut)

{

IUnknown *pUnk;
*ppvObjOut = NULL;
// TODO: if you want to support any additional interfaces, then you should

// indicate that here. never forget to call COleControl's version in the

// case where you don't support the given interface.

//

if(DO_GUIDS_MATCH(riid, IID_IBCFControl))

pUnk = (IUnknown *)(IBCFControl *)this;

else if(DO_GUIDS_MATCH(riid, IID_IPerPropertyBrowsing))

pUnk = (IUnknown *)(IPerPropertyBrowsing *)this;

else

return COleControl::InternalQueryInterface(riid, ppvObjOut);
pUnk->AddRef();

*ppvObjOut = (void *)pUnk;

return S_OK;
}

Your last requirement is to implement the functions of the interface (see Listing 11.13).

MapPropertyToPage is not required for your implementation, so you just return the constant E_NOTIMPL. MapPropertyToPage is used to connect the property to a property page that is implemented either in the container or in the control.

GetPredefinedStrings is the first function to be called. 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. The strings are placed in a list from which the user of the control can select the appropriate value to set the property to. In this case, the cookie value that is supplied is also the value that will be stored in the control's property.

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

After the property has been set with its value, the property browser calls the function GetDisplayString to get the string that is associated with the current property setting.

NOTE: It may seem a little redundant to have the method GetDisplayString when the property browser already has the string for the value from the GetPredefinedStrings function. The GetDisplayString function can be implemented without implementing the other methods. Implementing GetDisplayString without implementing the other functions in the IPerPropertyBrowsing interface is for those property types that do not use the standard property selection mechanism, for example, font selection, which uses a color selection dialog rather than a list of choices. The name of the font is retrieved via the GetDisplayString function, but the property selection facility is provided through a standard font dialog.

Listing 11.13 BCFCONTROLCTL.CPP--IPerPropertyBrowsing Implementation

STDMETHODIMP CBCFControlControl::MapPropertyToPage(DISPID, LPCLSID)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::GetPredefinedStrings(DISPID Dispid,

CALPOLESTR * lpcaStringsOut, CADWORD * lpcaCookiesOut)

{

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] = OLESTRFROMANSI(EALIGN_LEFT_TEXT);

lpcaStringsOut->pElems[1] = OLESTRFROMANSI(EALIGN_CENTER_TEXT);

lpcaStringsOut->pElems[2] = OLESTRFROMANSI(EALIGN_RIGHT_TEXT);
// assign the cookie value

lpcaCookiesOut->pElems[0] = EALIGN_LEFT;

lpcaCookiesOut->pElems[1] = EALIGN_CENTER;

lpcaCookiesOut->pElems[2] = EALIGN_RIGHT;
hResult = S_OK;

}
return hResult;

}
STDMETHODIMP CBCFControlControl::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;

}
STDMETHODIMP CBCFControlControl::GetDisplayString(DISPID Dispid, BSTR* lpbstr)

{

HRESULT hResult = S_FALSE;
// which property is it

switch(Dispid)

{

case dispidAlignment:

{

switch(m_state.lAlignment)

{

case EALIGN_LEFT:

*lpbstr = BSTRFROMANSI(EALIGN_LEFT_TEXT);

break;

case EALIGN_CENTER:

*lpbstr = BSTRFROMANSI(EALIGN_CENTER_TEXT);

break;

case EALIGN_RIGHT:

*lpbstr = BSTRFROMANSI(EALIGN_RIGHT_TEXT);

break;

}
// set the return value

hResult = S_OK;

}

break;

}
return hResult;
}

Drawing the Control

Optimized drawing allows you to create drawing objects, such as pens or brushes. Rather than remove 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. One thing to remember, though, is that 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 BaseCtl (like MFC and ATL), the scope of optimized drawing is very narrow compared to the OC 96 specification, but its use can nonetheless result in performance improvements. The OC 96 specification further breaks optimized drawing into what is known as aspects. For more information on aspect drawing, please see the OC 96 specification that ships with the ActiveX SDK.

Optimized Drawing

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

Listing 11.14 shows the optimized portion of your drawing implementation. If the container doesn't support optimized drawing, you select the original brush back into the Device Context (DC), and you destroy the brush you created. The next time that the OnDraw function is executed, you re-create the brush. When using optimized, you simply reuse the existing brush.

Listing 11.14 BCFCONTROLCTL.CPP--Optimized Drawing

. . .
// **

if(hOldFont)

// select the old object

::SelectObject(hdcDraw, hOldFont);
// increment the ref count so the font doesn't drop

// out from under us

if(m_pFont && hFont)

m_pFont->ReleaseHfont(hFont);

// **

// ****** Get the text font ******
// The container does not support optimized drawing.

if(!fOptimize)

{

// select the old brush back

::SelectObject(hdcDraw, hOldBrush);
// destroy the brush we created

::DeleteObject(hBrush);
// clear the brush handles

hBrush = hOldBrush = NULL;

}
return S_OK;

}

If the container supports optimized drawing, the final implementation detail is to destroy any resources that may still be active, which you do in the BeforeDestroyWindow function. Listing 11.15 shows the implementation that restores the original brush and destroys the brush that you created.

Listing 11.15 BCFCONTROLCTL.CPP--BeforeDestroyWindow Implementation

void CBCFControlControl::BeforeDestroyWindow(void)

{

// if there is an old brush

if(hOldBrush)

{

// get the DC

HDC hDC = this->OcxGetDC();
// select the old brush back

::SelectObject(hDC, hOldBrush);
// release the DC

this->OcxReleaseDC(hDC);

}
// if we created a brush

if(hBrush)

// destroy the brush we created

::DeleteObject(hBrush);
}

The fact is the user will not care how great your code is written or how many whiz-bang features it supports if it doesn't draw well. You'll be wise to spend some time on your drawing implementation and get it right.

Adding Clipboard and Drag and Drop Support

The basic OLE Clipboard and Drag and Drop interfaces are not implemented within the BaseCtl framework. As with the IPerPropertyBrowsing interface (see the section "Dynamic Property Enumeration"), you must implement the required interfaces yourself. As is pointed out in Chapters 7 and 9, Clipboard and Drag and Drop support can add much to your control implementation, while requiring very little work.

Clipboard Support

Clipboard support is based on the IDataObject and IEnumFORMATETC interfaces. IDataObject is the interface through which the data is retrieved from your object 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.

Using Built-In Clipboard Formats
As is pointed out in Chapter 7, the Windows operating system (OS) supports a number of built-in formats for transferring data via the Clipboard. Your first implementation will be to transfer your caption using the CF_TEXT format, which is the built-in format for transferring ANSI text. There are two aspects to using the Clipboard: being a Clipboard source and being a Clipboard target. You will first look at enabling your control as a Clipboard source.

Enabling a Control as a Clipboard Source
A Clipboard source is an application that puts data on the Clipboard for other applications to copy. You need to support two interfaces to enable your control as a Clipboard source: IDataObject and IEnumFORMATETC. 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 must take a snapshot of the data that the control contains and place it in a STGMEDIUM object. You have to do this because the data may not be copied from the Clipboard immediately and the data needs to reflect the state of the control when the copy operation was initiated rather than when the paste operation takes place. 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.

First you need to declare your COM interfaces for supporting the IDataObject and IEnumFORMATETC interfaces (see Listings 11.16 and 11.17).

Listing 11.16 IDATAOBJECT.H--IDataObject Interface Macro

#define DECLARE_STANDARD_DATAOBJECT() \
STDMETHOD(GetData)(LPFORMATETC, LPSTGMEDIUM); \
STDMETHOD(GetDataHere)(LPFORMATETC, LPSTGMEDIUM); \
STDMETHOD(QueryGetData)(LPFORMATETC); \
STDMETHOD(GetCanonicalFormatEtc)(LPFORMATETC, LPFORMATETC); \
STDMETHOD(SetData)(LPFORMATETC, LPSTGMEDIUM, BOOL); \
STDMETHOD(EnumFormatEtc)(DWORD, LPENUMFORMATETC*); \
STDMETHOD(DAdvise)(LPFORMATETC, DWORD, LPADVISESINK, LPDWORD); \
STDMETHOD(DUnadvise)(DWORD); \
STDMETHOD(EnumDAdvise)(LPENUMSTATDATA*);

Listing 11.17 IENUMFORMATETC--IEnumFORMATETC Interface Macro

#define DECLARE_STANDARD_ENUMFORMATETC() \
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);

You need to include the header files of your new interface macros, add the IDataObject and IEnumFORMATETC interfaces to your inheritance structure, and add the interface macros to your control declaration. You also need to add some functions and member variables to aid in your Clipboard support implementation (see Listing 11.18).

You use the functions CopyStgMedium, CopyDataToClipboard, and PrepareDataForTransfer to prepare your data structures--the member variables sTextFormatEtc and sTextStgMedium-- for a potential paste operation. The member variable ulFORMATETCElement is the internal counter for the FORMATETC enumerator interface, and OnKeyDown is where all of your Clipboard operations are initiated.

Listing 11.18 BCFCONTROLCTL.H--Clipboard Support Implementation--Header File

. . .
#include "IPerPropertyBrowsing.h"
#include "IDataObject.h"
#include "IEnumFORMATETC.h"
#include "alignmentenums.h"
typedef enum
{
BCFControlEvent_Change = 0,
} BCFCONTROLEVENTS;
. . .
//
class CBCFControlControl : public CInternetControl, public IBCFControl,
public ISupportErrorInfo, public IPerPropertyBrowsing, public IDataObject,
public IEnumFORMATETC
{
public:
. . . // ISupportErrorInfo methods
//

DECLARE_STANDARD_SUPPORTERRORINFO();
// IPerPropertyBrowsing methods

//

DECLARE_STANDARD_PERPROPERTYBROWSING();
// IDataObject methods

//

DECLARE_STANDARD_DATAOBJECT();
// IEnumFORMATETC methods

//

DECLARE_STANDARD_ENUMFORMATETC();
// IBCFControl methods

//

STDMETHOD(get_Alignment)(THIS_ long FAR* lRetValue);

STDMETHOD(put_Alignment)(THIS_ long lNewValue);

STDMETHOD(get_BackColor)(THIS_ OLE_COLOR FAR* ocRetValue);
. . .
void GetTextExtent(HDC hDC, LPCTSTR lpctstrString, int & cx, int & cy);

BOOL bRetrievedDimensions;

int iCharWidthArray[256];

int iCharacterSpacing, iCharacterHeight;

void CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium, LPSTGMEDIUM lpSourceStgMedium,

CLIPFORMAT cfSourceFormat);

void CopyDataToClipboard(void);

void PrepareDataForTransfer(void);

ULONG ulFORMATETCElement;

void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
private:

FORMATETC sTextFormatEtc;

STGMEDIUM sTextStgMedium;
};

You need to update the constructor to initialize your enumerator to the beginning of the enumeration (see Listing 11.19).

Listing 11.19 BCFCONTROLCTL.CPP--Constructor Member Initialization

. . .
hOldBrush = hBrush = NULL;
// clear the flag

bRetrievedDimensions = FALSE;
// set to the first element

ulFORMATETCElement = 0;
// clear the storage medium

sTextStgMedium.hGlobal = NULL;

}
#pragma warning(default:4355) // using `this' in constructor

Since you have added two additional COM interfaces to your control, you also need to update your QueryInterface implementation (see Listing 11.20).

Listing 11.20 BCFCONTROLCTL.CPP--QueryInterface Update

HRESULT CBCFControlControl::InternalQueryInterface(REFIID riid, void **ppvObjOut)

{

IUnknown *pUnk;
*ppvObjOut = NULL;
// TODO: if you want to support any additional interfaces, then you should

// indicate that here. never forget to call COleControl's version in the

// case where you don't support the given interface.

//

if(DO_GUIDS_MATCH(riid, IID_IBCFControl))

pUnk = (IUnknown *)(IBCFControl *)this;

else if(DO_GUIDS_MATCH(riid, IID_IPerPropertyBrowsing))

pUnk = (IUnknown *)(IPerPropertyBrowsing *)this;

else if(DO_GUIDS_MATCH(riid, IID_IDataObject))

pUnk = (IUnknown *)(IDataObject *)this;

else if(DO_GUIDS_MATCH(riid, IID_IEnumFORMATETC))

pUnk = (IUnknown *)(IEnumFORMATETC *)this;

else

return COleControl::InternalQueryInterface(riid, ppvObjOut);
pUnk->AddRef();

*ppvObjOut = (void *)pUnk;

return S_OK;
}

You also need to update your WindowProc function to look for the WM_KEYDOWN message so that you can process the keystrokes that will initiate your Clipboard data transfer (see Listing 11.21).

NOTE: Listing 11.21 contains a switch statement that is used to route Windows messages to the proper message handler. The default handler will call the method OcxDefWindowProc. Whenever you want to use the default implementation for a message, you call OcxDefWindowProc. OcxDefWindowProc is designed to deal with the cases when the control does not have a window handle, because the control may have been created as windowless. Remember that the control will not have its own window handle when it is created windowless, so you should never use the handle directly. Always allow the default BaseCtl implementation to handle the windowless processing of messages.

Listing 11.21 BCFCONTROLCTL.CPP--WM_KEYDOWN Message Handler

LRESULT CBCFControlControl::WindowProc

(

UINT msg,

WPARAM wParam,

LPARAM lParam

)

{

// TODO: handle any messages here, like in a normal window

// proc. note that for special keys, you'll want to override and

// implement OnSpecialKey.

// if you're a windowed OCX, you should be able to use any of the

// win32 API routines except for SetFocus. you should always use

// OcxSetFocus()

//
LRESULT lRetVal = FALSE;
switch(msg)

{

case WM_KEYDOWN:

this->OnKeyDown(wParam, LOWORD(lParam), HIWORD(lParam));

break;

default:

lRetVal = OcxDefWindowProc(msg, wParam, lParam);

break;

}
return lRetVal;
}

Finally you need to add all of the code for the methods that you declared in your header file. Take a look at all of the methods in detail.

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

Listing 11.22 BCFCONTROLCTL.CPP--CopyDataToClipboard Implementation

void CBCFControlControl::CopyDataToClipboard(void)

{

BOOL bHaveClipboard = TRUE;
// if we don't have an IDataObject on the clipboard?

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

// set the flag

bHaveClipboard = FALSE;
// put data in the storage

this->PrepareDataForTransfer();
// if we don't have the clipboard

if(!bHaveClipboard)

{

// clear the clipboard

::OleFlushClipboard();
// put the data on the clipboard

::OleSetClipboard((IDataObject *) this);

}
}

PrepareDataForTransfer (see Listing 11.23) is the function you call when you want to copy the data from your control to the STGMEDIUM structure that will represent your data on the Clipboard. First you allocate a block of global memory that will contain your caption in ANSI format. Then you set up your FORMATETC and STGMEDIUM structures to reflect the correct data type.

Listing 11.23 BCFCONTROLCTL.CPP--PrepareDataForTransfer Implementation

void CBCFControlControl::PrepareDataForTransfer(void)

{

// get the length of the data to copy

long lLength = lstrlen(m_lptstrCaption) + 1;
// create a global memory object

HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,

sizeof(TCHAR) * lLength);



// lock the memory down

LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(hGlobal);
// copy the string

for(long lCount = 0; lCount < lLength - 1; lCount++)

lpTempBuffer[lCount] = m_lptstrCaption[lCount];
// null terminate the string

lpTempBuffer[lCount] = `\0';
// unlock the memory

::GlobalUnlock(hGlobal);
// copy all of the members

sTextFormatEtc.cfFormat = CF_TEXT;

sTextFormatEtc.ptd = NULL;

sTextFormatEtc.dwAspect = 0;

sTextFormatEtc.lindex = -1;

sTextFormatEtc.tymed = TYMED_HGLOBAL;
// if we have already allocated the data

if(sTextStgMedium.hGlobal)

// release it

::ReleaseStgMedium(&sTextStgMedium);
sTextStgMedium.tymed = TYMED_HGLOBAL;

sTextStgMedium.hGlobal = hGlobal;

sTextStgMedium.pUnkForRelease = NULL;
}

CopyStgMedium (see Listing 11.24) is a simple helper function to copy one STGMEDIUM structure to another. The function relies on the OleDuplicateData function to create a new copy of the global memory stored in the source STGMEDIUM. The copied data is then stored in the target STGMEDIUM structure.

Listing 11.24 BCFCONTROLCTL.CPP--CopyStgMedium Implementation

void CBCFControlControl::CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,

LPSTGMEDIUM lpSourceStgMedium, CLIPFORMAT cfSourceFormat)

{

// copy the stgmedium members

lpTargetStgMedium->tymed = lpSourceStgMedium->tymed;

lpTargetStgMedium->pUnkForRelease = lpSourceStgMedium->pUnkForRelease;

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

cfSourceFormat, GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT);
}

The next set of functions (see Listing 11.25) are implemented for the IDataObject interface that you declared in your header file. A number of methods are not implemented and return the value E_NOTIMPL because they are not needed for this implementation.

GetData is the function called when you need to copy the data in your STGMEDIUM structure to the STGMEDIUM structure that is supplied. You first see whether the format that is requested matches the data that you support and, if so, copy the data using your helper function.

EnumFormatEtc is the method called when the requesting application wants to enumerate your supported formats. You support only the DATADIR_GET direction, which means you can support only the GetData function and not the SetData function of the IDataObject interface.

The remainder of the functions are not implemented and simply return the constant E_NOTIMPL.

Listing 11.25 BCFCONTROLCTL.CPP--IDataObject Implementation

STDMETHODIMP CBCFControlControl::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 DATA_E_FORMATETC;

}
STDMETHODIMP CBCFControlControl::GetDataHere(LPFORMATETC /*lpFormatEtc*/,

LPSTGMEDIUM /*lpStgMedium*/)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::QueryGetData(LPFORMATETC /*lpFormatEtc*/)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::GetCanonicalFormatEtc(LPFORMATETC /*lpFormatEtcIn*/,

LPFORMATETC /*lpFormatEtcOut*/)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::SetData(LPFORMATETC /*lpFormatEtc*/,

LPSTGMEDIUM /*lpStgMedium*/,

BOOL /*bRelease*/)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::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 E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::DAdvise(FORMATETC * /*pFormatEtc*/, DWORD /*advf*/,

LPADVISESINK /*pAdvSink*/, DWORD * /*pdwConnection*/)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::DUnadvise(DWORD /*dwConnection*/)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::EnumDAdvise(LPENUMSTATDATA * /*ppenumAdvise*/)

{

return E_NOTIMPL;
}

The next set of functions (see Listing 11.26) are implemented for the IEnumFORMATETC interface that you declared in your header file. Cloning is not supported and will return the value E_NOTIMPL.

The Next function is used to enumerate through the entire list of supported formats. You first check to see whether your counter is set to the first element and that the user asked for at least one entry. If so, you set the FORMATETC structure to your supported format, and if appropriate, you set the number of elements that you are returning and increment the counter.

The Skip function advances the enumerator by the number of elements specified.

The Reset function sets the enumerator back to the beginning of the enumeration.

Listing 11.26 BCFCONTROLCTL.CPP--IEnumFORMATETC Implementation

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

ULONG RPC_FAR * pceltFetched)

{

// if we are at the beginning of the enumeration

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

{

// copy all of the members

rgelt->cfFormat = CF_TEXT;

rgelt->ptd = NULL;

rgelt->dwAspect = 0;

rgelt->lindex = -1;

rgelt->tymed = TYMED_HGLOBAL;



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

if(pceltFetched)

*pceltFetched = 1;
// increment the counter

ulFORMATETCElement++;
// return success

return S_OK;

}

else

// return failure

return S_FALSE;

}
STDMETHODIMP CBCFControlControl::Skip(ULONG celt)

{

// move the counter by the number of elements supplied

ulFORMATETCElement += celt;



// return success

return S_OK;

}
STDMETHODIMP CBCFControlControl::Reset(void)

{

// reset to the beginning of the enumerator

ulFORMATETCElement = 0;



// return success

return S_OK;

}
STDMETHODIMP CBCFControlControl::Clone(IEnumFORMATETC RPC_FAR *__RPC_FAR * /*ppenum*/)

{

return E_NOTIMPL;
}

Finally you are at the end of your implementation: the OnKeyDown function (see Listing 11.27). The OnKeyDown contains all of the code that is necessary to look for the common keystroke combinations used to initiate Clipboard operations.

Listing 11.27 BCFCONTROLCTL.CPP--OnKeyDown Implementation

void CBCFControlControl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

BOOL bHandled = FALSE;
// find out if the shift key is being held down

short sShift = ::GetKeyState(VK_SHIFT);

// find out if the control key is being held down

short sControl = ::GetKeyState(VK_CONTROL);
switch(nChar)

{

// COPY or PASTE

case 0x43: // `C'

case 0x63: // `c'

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

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->InvalidateControl(NULL);
// we don't need to pass this key to the base implementation

bHandled = TRUE;

}

break;

}
// if we didn't handle the character

if(!bHandled)

{

// and the control key is not being held down

if(!(sControl & 0x8000))

// send to the default handler

this->OcxDefWindowProc(WM_KEYDOWN, (WPARAM) nFlags,

MAKELPARAM(nRepCnt, nFlags));

}
}

Now that you know how to put data on the Clipboard, take a look at how you get data off the Clipboard.

Enabling a Control as a Clipboard Target
The opposite of being a Clipboard source is being a Clipboard target. The first step in enabling your control as a Clipboard target is to update your header file with two additional member functions: GetDataFromClipboard and GetDataFromTransfer (see Listing 11.28).

Listing 11.28 BCFCONTROCTL.H--Clipboard Target Implementation-- Header File

. . .

void CopyDataToClipboard(void);

void PrepareDataForTransfer(void);

void GetDataFromClipboard(void);

void GetDataFromTransfer(IDataObject * ipDataObj);

ULONG ulFORMATETCElement;

void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
private:

FORMATETC sTextFormatEtc;
. . .

The next step is to update your source file with the GetDataFromClipboard and GetDataFromTransfer implementations (see Listing 11.29). The first method, GetDataFromClipboard, which, as the name implies, gets the data from the Clipboard and transfers it to your control. GetDataFromClipboard first checks the Clipboard to see whether you already own it. If you do, you refresh the control's data with the data that is stored in the STGMEDIUM structure. You do this because the data stored in the control may have changed since the data was originally pasted to the Clipboard.

If you don't already own the Clipboard, you get the IDataObject reference of the object that does and pass it on to your GetDataFromTransfer function.

Listing 11.29 BCFCONTROLCTL.CPP--GetDataFromClipboard Implementation

void CBCFControlControl::GetDataFromClipboard(void)

{

// get an IDataObject pointer

IDataObject * ipClipboardDataObj = NULL;
// do we have an IDataObject on the clipboard?

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

{

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

LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(sTextStgMedium.hGlobal);
// if we have a string

if(m_lptstrCaption)

{

// delete the existing string

delete [] m_lptstrCaption;
// clear the reference just to be safe

m_lptstrCaption = NULL;

}
// allocate a new string

m_lptstrCaption = new TCHAR[lstrlen(lpTempBuffer) + 1];
// assign the string to our member variable

lstrcpy(m_lptstrCaption, lpTempBuffer);
// unlock the memory

::GlobalUnlock(sTextStgMedium.hGlobal);
return;

}

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

{

// transfer the data to the control

this->GetDataFromTransfer(ipClipboardDataObj);
// release the IDataObject

ipClipboardDataObj->Release();

}
}

GetDataFromTransfer requests the IEnumFORMATETC interface from the IDataObject and cycles through all of the supported formats looking for one that matches yours (see Listing 11.30). Upon finding the appropriate format, it requests the data from the IDataObject supplying a FORMATETC and STGMEDIUM structure. The data is transferred to the control, and the STGMEDUIM is released. The next thing you do is release your interface pointers. The last step, if you find a format, is to force the control to repaint itself reflecting the new state of the control.

Listing 11.30 BCFCONTROLCTL.CPP--GetDataFromTransfer Implementation

void CBCFControlControl::GetDataFromTransfer(IDataObject * ipDataObj)

{

IEnumFORMATETC * ipenumFormatetc;

BOOL bFound = FALSE;
// get a FORMATETC enumerator

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

{

// reset the enumerator just to be safe

ipenumFormatetc->Reset();
FORMATETC etc;
// while there are formats to enumerate

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

{

// 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);
// terminate the loop

bFound = TRUE;

}

}

}



// release the enumerator

ipenumFormatetc->Release();

}
// if we found a format

if(bFound == TRUE)

// force the control to repaint itself

this->InvalidateControl(NULL);
}

Last you add the code that will initiate the transfer; you do this in your OnKeyDown function (see Listing 11.31).

Listing 11.31 BCFCONTROCTL.CPP--OnKeyDown Implementation

void CBCFControlControl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

BOOL bHandled = FALSE;
// find out if the shift key is being held down

short sShift = ::GetKeyState(VK_SHIFT);

// find out if the control key is being held down

short sControl = ::GetKeyState(VK_CONTROL);
switch(nChar)

{

// 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 redraw itself

this->InvalidateControl(NULL);
// 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 redraw itself

this->InvalidateControl(NULL);
// we don't need to pass this key to the base implementation

bHandled = TRUE;

}

break;

case 0x58: // `X'

case 0x78: // `x'

case VK_DELETE:

// if this is a shift delete OR CTRL-X/x
. . .

While not as simple to implement as MFC, Clipboard support in a BaseCtl implementation can be added in a relatively short period of time with very satisfying results.

To round out your implementation, you take the next logical step, which is Drag and Drop support.

Adding Drag and Drop Support

The fundamentals of Drag and Drop are very similar to Clipboard support. In addition to the Clipboard interfaces, Drag and Drop requires two new interfaces: IDropSource and IDropTarget. IDropSource is for those controls that can create data that can be dropped onto another application. IDropTarget is for those controls that can accept data that has been dropped from another application.

Using Built-In Drag and Drop Formats
Since Drag and Drop is similar to a Clipboard transfer, it relies on the same built-in data formats as Clipboard transfers. See the section entitled "Using Built-In Clipboard Formats" for more information regarding the types of formats available. Enabling a BaseCtl control implementation for Drag and Drop support is similar in complexity to the work you did to enable Clipboard transfers.

Enabling a Control as a Drag and Drop Source
The first part of your implementation is to enable your control as a Drag and Drop source. To be a Drag and Drop source, the control must implement the IDropSource interface in addition to the IDataObject and IEnumFORMATETC interfaces. The IDropSource interface is declared in the same manner as your other COM interfaces (see Listing 11.32). The implementation is fairly simple since the interface consists of only two methods.

Listing 11.32 IDROPSOURCE.H--IDropSource Interface

#define DECLARE_STANDARD_DROPSOURCE() \
STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD dwKeyState); \
STDMETHOD(GiveFeedback)(DWORD dwEffect);

Next you add the include file for the new interface, inherit your control from the interface, and add your interface macro to your control implementation (see Listing 11.33). You also add the prototype for OnLButtonDown, which is the function that initiates your Drag and Drop operation.

Listing 11.33 BCFCONTROCTL.H--IDropSource Interface Implementation

. . .
#include "IEnumFORMATETC.h"

#include "IDropSource.h"
#include "alignmentenums.h"
. . .
class CBCFControlControl : public CInternetControl, public IBCFControl,

public ISupportErrorInfo, public IPerPropertyBrowsing, public IDataObject,

public IEnumFORMATETC, public IDropSource

{

public:
. . .
// IDropSource methods

//

DECLARE_STANDARD_DROPSOURCE();
// IBCFControl methods

//

STDMETHOD(get_Alignment)(THIS_ long FAR* lRetValue);

STDMETHOD(put_Alignment)(THIS_ long lNewValue);
. . .
ULONG ulFORMATETCElement;

void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);

void OnLButtonDown(UINT nFlags, short sHor, short sVer);
private:

FORMATETC sTextFormatEtc;

STGMEDIUM sTextStgMedium;

}; . . .

Since you added a new interface, you also need to update your QueryInterface function (see Listing 11.34).

Listing 11.34 BCFCONTROLCTL.CPP--QueryInterface Update

HRESULT CBCFControlControl::InternalQueryInterface(REFIID riid, void
**ppvObjOut)

{

IUnknown *pUnk;
*ppvObjOut = NULL;
// TODO: if you want to support any additional interfaces, then you should

// indicate that here. never forget to call COleControl's version in the

// case where you don't support the given interface.

//

if(DO_GUIDS_MATCH(riid, IID_IBCFControl))

pUnk = (IUnknown *)(IBCFControl *)this;

else if(DO_GUIDS_MATCH(riid, IID_IPerPropertyBrowsing))

pUnk = (IUnknown *)(IPerPropertyBrowsing *)this;

else if(DO_GUIDS_MATCH(riid, IID_IDataObject))

pUnk = (IUnknown *)(IDataObject *)this;

else if(DO_GUIDS_MATCH(riid, IID_IEnumFORMATETC))

pUnk = (IUnknown *)(IEnumFORMATETC *)this;

else if(DO_GUIDS_MATCH(riid, IID_IDropSource))

pUnk = (IUnknown *)(IDropSource *)this;

else

return COleControl::InternalQueryInterface(riid, ppvObjOut);
pUnk->AddRef();

*ppvObjOut = (void *)pUnk;

return S_OK;
}

You have to update your WindowProc function to route the WM_LBUTTONDOWN messages to your OnLButtonDown function (see Listing 11.35).

Listing 11.35 BCFCONTROLCTL.CPP--WindowProc Implementation

LRESULT CBCFControlControl::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam)

{

LRESULT lRetVal = FALSE;
switch(msg)

{

case WM_KEYDOWN:

this->OnKeyDown(wParam, LOWORD(lParam), HIWORD(lParam));

break;

case WM_LBUTTONDOWN:

this->OnLButtonDown(wParam, (short) LOWORD(lParam), (short) HIWORD(lParam));

this->OcxDefWindowProc(msg, wParam, lParam);

break;

default:

lRetVal = this->OcxDefWindowProc(msg, wParam, lParam);

break;

}
return lRetVal;
}

Next you need to implement your OnLButtonDown function and the two interface functions for your IDropSource interface (see Listing 11.36).

When OnLButtonDown is called, the control calls your PrepareDataForTransfer function to set up the data in your IDataObject interface. You then call DoDragDrop supplying the IDataObject and IDropSource interfaces and the drop effect that you want to display. See the VC++ documentation for more information about the IDataObject and IDataSource interfaces and the types of drop effects that you have at your disposal.

QueryContinueDrag is used to instruct OLE as to how the Drag and Drop operation should be handled at the time the state of the keyboard or mouse changes. When the keyboard or mouse state changes, it is usually indicative of a drop operation. In your case, you look 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 your case, you use the default cursors. See the VC++ documentation for more information on how to support different cursors.

Listing 11.36 BCFCONTROLCTL.CPP--Drop Source Implementation

void CBCFControlControl::OnLButtonDown(UINT nFlags, short sHor, short sVer)

{

// call the common data preparation function

this->PrepareDataForTransfer();
DWORD dwDropEffect = DROPEFFECT_NONE;



// start the Drag and Drop operation

::DoDragDrop((IDataObject *) this, (IDropSource *) this, DROPEFFECT_COPY,

&dwDropEffect);

}
STDMETHODIMP CBCFControlControl::QueryContinueDrag(BOOL fEscapePressed,

DWORD dwKeyState)

{

// if the left button has been released

if(!(dwKeyState & MK_LBUTTON))

// it is OK to drop

return DRAGDROP_S_DROP;

else

// return success

return S_OK;

}
STDMETHODIMP CBCFControlControl::GiveFeedback(DWORD dwEffect)

{

// use the default cursors

return DRAGDROP_S_USEDEFAULTCURSORS;

}

As you can see, very little code is required to be a Drag and Drop source, but being a Drag and Drop source is only half the battle. To develop a truly complete implementation, you need to include support as a Drag and Drop target.

Enabling a Control as a Drag and Drop Target
As with your Drag and Drop source support, you build upon the interfaces you've already created and add some new functionality. To be a Drag and Drop target, a control must implement the IDropTarget interface (see Listing 11.37).

Listing 11.37 IDROPTARGET.H--IDropTarget Interface

#define DECLARE_STANDARD_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);

Now that you have your interface macro declared, you need to add the interface to your control implementation. To do this, you need to add your macro include file, inherit from the COM interface, and add the IDropTarget macro to your control (see Listing 11.38). You also add the AfterCreateWindow function, which is defined in the COleControl base class. AfterCreateWindow is where you register your control as a valid Drag and Drop target.

Listing 11.38 BCFCONTROLCTL.H--IDropTarget Implementation

. . .
#include "IDataObject.h"

#include "IEnumFORMATETC.h"

#include "IDropSource.h"

#include "IDropTarget.h"
#include "alignmentenums.h"
. . .
class CBCFControlControl : public CInternetControl, public IBCFControl,

public ISupportErrorInfo, public IPerPropertyBrowsing, public IDataObject,

public IEnumFORMATETC, public IDropSource, public IDropTarget

{

public:
. . .
// IDropTarget methods

//

DECLARE_STANDARD_IDROPTARGET();
// IBCFControl methods

//
. . .
virtual HRESULT InternalQueryInterface(REFIID, void **);

virtual BOOL BeforeCreateWindow(DWORD *pdwWindowStyle, DWORD *pdwExWindowStyle,

LPSTR pszWindowTitle);

virtual void BeforeDestroyWindow(void);

virtual BOOL AfterCreateWindow(void);
/// OnData is called asynchronously as data for an object or property arrives...

virtual HRESULT OnData(DISPID propId, DWORD bscfFlag, IStream * strm, DWORD dwSize);
// private state information.

// . . .

Since you have a COM interface, you also need to update your QueryInterface function (see Listing 11.39).

Listing 11.39 BCFCONTROLCTL.CPP--QueryInterface Update



HRESULT CBCFControlControl::InternalQueryInterface(REFIID riid, void **ppvObjOut)

{

IUnknown *pUnk;
*ppvObjOut = NULL;
// TODO: if you want to support any additional interfaces, then you should

// indicate that here. never forget to call COleControl's version in the

// case where you don't support the given interface.

//

if(DO_GUIDS_MATCH(riid, IID_IBCFControl))

pUnk = (IUnknown *)(IBCFControl *)this;

else if(DO_GUIDS_MATCH(riid, IID_IPerPropertyBrowsing))

pUnk = (IUnknown *)(IPerPropertyBrowsing *)this;

else if(DO_GUIDS_MATCH(riid, IID_IDataObject))

pUnk = (IUnknown *)(IDataObject *)this;

else if(DO_GUIDS_MATCH(riid, IID_IEnumFORMATETC))

pUnk = (IUnknown *)(IEnumFORMATETC *)this;

else if(DO_GUIDS_MATCH(riid, IID_IDropSource))

pUnk = (IUnknown *)(IDropSource *)this;

else if(DO_GUIDS_MATCH(riid, IID_IDropTarget))

pUnk = (IUnknown *)(IDropTarget *)this;

else

return COleControl::InternalQueryInterface(riid, ppvObjOut);
pUnk->AddRef();

*ppvObjOut = (void *)pUnk;

return S_OK;
}

In order for your control to be a Drop Target, it must do more than just support the appropriate interfaces. You must register your control as a Drop Target. You do this in the AfterCreateWindow function you added earlier. Listing 11.40 shows your implementation of your Register and, correspondingly, your Revoke implementation. For this reason alone, your control must have a window. Controls that support Windowless Activation can still be Drag and Drop targets, but they must create a window when a Drag and Drop operation occurs. See the VC++ documentation for more information on supporting Drag and Drop operations in windowless controls.

Listing 11.40 BCFCONTROLCTL.CPP--AfterCreateWindow Implementation

BOOL CBCFControlControl::AfterCreateWindow(void)

{

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

}
void CBCFControlControl::BeforeDestroyWindow(void)

{

// if we have a window handle

if(m_hwnd)

// revoke the control as a drag and drop target

::RevokeDragDrop(m_hwnd);
// if there is an old brush

if(hOldBrush)

{

// get the DC

HDC hDC = this->OcxGetDC();
// select the old brush back

::SelectObject(hDC, hOldBrush);
// release the DC

this->OcxReleaseDC(hDC);

}
// if we created a brush

if(hBrush)

// destroy the brush we created

::DeleteObject(hBrush);
}

Last you implement your IDropTarget interface (see Listing 11.41).

DragEnter is where you instruct OLE as to whether the current drag operation that has entered your control is valid for your 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 if the IDataObject that was passed to you contains formats that you can use.

DragOver is used to instruct Windows as to the current state that the drag operation is in while it is over your control. Your implementation is very easy. 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 allow text and numeric data to be dropped 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 your control using the GetDataFromTransfer method.

Listing 11.41 BCFCONTROLCTL.CPP--IDropTarget Implementation

STDMETHODIMP CBCFControlControl::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 CBCFControlControl::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 CBCFControlControl::DragLeave(void)

{

return E_NOTIMPL;

}
STDMETHODIMP CBCFControlControl::Drop(LPDATAOBJECT pDataObject, DWORD dwKeyState,

POINTL pt, LPDWORD pdwEffect)

{

// transfer the data to the control

this->GetDataFromTransfer(pDataObject);
// return success

return S_OK;
}

As with your MFC and ATL implementations, adding Drag and Drop support is straightforward. Now that you have addressed the built-in formats, take a look at the next step, custom formats.

Custom Clipboard and Drag and Drop Formats

A custom data format is one that is understood by the exchanging applications but does not fall into the category of predefined formats. For your implementation, you transfer the text Alignment property along with your 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 Clipboard and Drag and Drop operations.

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

Listing 11.42 BCFCONTROLCTL.H--Custom Data Format Member Variables

. . .
private:

FORMATETC sTextFormatEtc;

STGMEDIUM sTextStgMedium;

// custom format storage variables

UINT m_uiCustomFormat;

FORMATETC sCustomFormatEtc;

STGMEDIUM sCustomStgMedium;
};

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

Listing 11.43 BCFCONTROLCTL.CPP--Register the Custom Format

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

}
#pragma warning(default:4355) // using `this' in constructor

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

Listing 11.44 BCFCONTROCTL.CPP--PrepareDataForTransfer Update

void CBCFControlControl::PrepareDataForTransfer(void)

{
. . .
// if we have custom clipboard format support

if(m_uiCustomFormat)

{

// create a global memory object

HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,

sizeof(m_state.lAlignment));



// lock the memory down

LONG * lpTempBuffer = (LONG *) ::GlobalLock(hGlobal);
// set our data buffer

*lpTempBuffer = m_state.lAlignment;
// unlock the memory

::GlobalUnlock(hGlobal);
// copy all of the members

sCustomFormatEtc.cfFormat = m_uiCustomFormat;

sCustomFormatEtc.ptd = NULL;

sCustomFormatEtc.dwAspect = 0;

sCustomFormatEtc.lindex = -1;

sCustomFormatEtc.tymed = TYMED_HGLOBAL;
// if we have already allocated the data

if(sCustomStgMedium.hGlobal)

// release it

::ReleaseStgMedium(&sCustomStgMedium);
sCustomStgMedium.tymed = TYMED_HGLOBAL;

sCustomStgMedium.hGlobal = hGlobal;

sCustomStgMedium.pUnkForRelease = NULL;

}
}

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

Listing 11.45 BCFCONTROLCTL.CPP--GetDataFromTransfer Update

void CBCFControlControl::GetDataFromTransfer(IDataObject * ipDataObj)

{

IEnumFORMATETC * ipenumFormatetc;

BOOL bFound = FALSE;
// get a FORMATETC enumerator

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

{

// reset the enumerator just to be safe

ipenumFormatetc->Reset();
FORMATETC etc;
// while there are formats to enumerate

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

{

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

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

{

STGMEDIUM sStgMediumData;
// get the data from the stgmedium

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

{

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

LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(sStgMediumData.hGlobal);
// if we have a string

if(m_lptstrCaption)

{

// delete the existing string

delete [] m_lptstrCaption;
// clear the reference just to be safe

m_lptstrCaption = NULL;

}
// allocate a new string

m_lptstrCaption = new TCHAR[lstrlen(lpTempBuffer) + 1];
// assign the string to our member variable

lstrcpy(m_lptstrCaption, lpTempBuffer);
// unlock the memory

::GlobalUnlock(sStgMediumData.hGlobal);
// release the storage medium

::ReleaseStgMedium(&sStgMediumData);
// indicate success

bFound = TRUE;

}

}

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

else if(m_uiCustomFormat && etc.cfFormat == m_uiCustomFormat && etc.tymed & TYMED_HGLOBAL)

{

STGMEDIUM sStgMediumData;
// get the data from the stgmedium

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

{

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

LONG * lpTempBuffer = (LONG *) ::GlobalLock(sStgMediumData.hGlobal);
// get the data

m_state.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->InvalidateControl(NULL);
}

Now that you have updated your basic data transfer routines, you need to update your IEnumFORMATETC interface to essentially publish the availability of the new format to any application that wants it. You do this in your IEnumFORMATETC::Next function (see Listing 11.46). If you support a custom format and are at the second format in your enumerator, you fill in the FORMATETC structure that was passed in with the appropriate information. Doing this will let any application that understands your custom format know that you can also support the custom format. Note that the implementation will return only a single format, even if the caller asked for more than one. You can add code to deal with the cases where more than one format is requested, but adding the additional code doesn't add anything to the sample, so that topic is not addressed here.

Listing 11.46 BCFCONTROLCTL.CPP--IEnumFORMATETC::Next Update

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

ULONG RPC_FAR * pceltFetched)

{

// if we are at the beginning of the enumeration

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

{

// copy all of the members

rgelt->cfFormat = CF_TEXT;

rgelt->ptd = NULL;

rgelt->dwAspect = 0;

rgelt->lindex = -1;

rgelt->tymed = TYMED_HGLOBAL;



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

if(pceltFetched)

*pceltFetched = 1;
// increment the counter

ulFORMATETCElement++;
// return success

return S_OK;

}

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

{

// copy all of the members

rgelt->cfFormat = m_uiCustomFormat;

rgelt->ptd = NULL;

rgelt->dwAspect = 0;

rgelt->lindex = -1;

rgelt->tymed = TYMED_HGLOBAL;



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

if(pceltFetched)

*pceltFetched = 1;
// increment the counter

ulFORMATETCElement++;
// return success

return S_OK;

}

else

// return failure

return S_FALSE;
}

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

Listing 11.47 BCFCONTROLCTL.CPP--IEnumFORMATETC::GetData Update

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

{

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

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

{

// get a copy of the current stgmedium

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

}

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

lpFormatEtc->tymed & TYMED_HGLOBAL)

{

// get a copy of the current stgmedium

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

}

else

return DATA_E_FORMATETC;
}

That is all it takes to support custom formats. By taking a "black box" approach to creating your data transfer routines (meaning that you create routines that manipulate basic data structures and remove the specific data transfer details from your code), you can support a large amount of functionality relying on a common code base.

Adding Clipboard and Drag and Drop support to your control can improve its overall appearance and integration with other controls and the container in which it resides.

Subclassing Existing Windows Controls

As with your MFC and ATL implementations, you can support the subclassing of existing Windows controls with the BaseCtl framework. At the beginning of Chapter 10, you created several controls in your application, one of which subclassed a Windows BUTTON control, CBCFControlSubControl. Take a look at the additional code that is required to support subclassing. Listing 11.48 shows the extent of your implementation.

RegisterClassData retrieves the class information for the BUTTON control and uses it for your control.

OnDraw delegates the painting of the control to the DoSuperClassPaint function.

WindowProc delegates the standard windows message handling to the subclassed control.

Listing 11.48 BCFCONTROLSUBCTL.CPP--RegisterClassData Implementation

//=-------------------------------------------------------------------------=

// CBCFControlSubWinControl:RegisterClassData

//=-------------------------------------------------------------------------=

// register the window class information for your control here.

// this information will automatically get cleaned up for you on DLL shutdown.

//

// Output:

// BOOL - FALSE means fatal error.

//

// Notes:

//

BOOL CBCFControlSubWinControl::RegisterClassData

(

void

)

{

WNDCLASS wndclass;
// subclass a windows BUTTON control.

//

if (!::GetClassInfo(g_hInstance, "BUTTON", &wndclass))

return FALSE;
// this doesn't need a critical section for apartment threading support

// since it's already in a critical section in CreateInPlaceWindow

//

SUBCLASSWNDPROCOFCONTROL(OBJECT_TYPE_CTLBCFCONTROLSUBWIN) =

(WNDPROC)wndclass.lpfnWndProc;

wndclass.lpfnWndProc = COleControl::ControlWindowProc;

wndclass.lpszClassName = WNDCLASSNAMEOFCONTROL(OBJECT_TYPE_CTLBCFCONTROLSUBWIN);
return RegisterClass(&wndclass);

}
. . .
//=-------------------------------------------------------------------------=

// CBCFControlSubWinControl::OnDraw

//=-------------------------------------------------------------------------=

// "I don't very much enjoy looking at paintings in general. i know too

// much about them. i take them apart."

// - georgia o'keeffe (1887-1986)

//

// Parameters:

// DWORD - [in] drawing aspect

// HDC - [in] HDC to draw to

// LPCRECTL - [in] rect we're drawing to

// LPCRECTL - [in] window extent and origin for meta-files

// HDC - [in] HIC for target device

// BOOL - [in] can we optimize DC handling?

//

// Output:

// HRESULT

//

// Notes:

//

HRESULT CBCFControlSubWinControl::OnDraw

(

DWORD dvAspect,

HDC hdcDraw,

LPCRECTL prcBounds,

LPCRECTL prcWBounds,

HDC hicTargetDevice,

BOOL fOptimize

)

{

// TODO: put your drawing code here ...

//

return DoSuperClassPaint(hdcDraw, prcBounds);

}
//=-------------------------------------------------------------------------=

// CBCFControlSubWinControl::WindowProc

//=-------------------------------------------------------------------------=

// window procedure for this control. nothing terribly exciting.

//

// Parameters:

// see win32sdk on window procs [except HWND -- it's in m_hwnd]

//

// Notes:

//

LRESULT CBCFControlSubWinControl::WindowProc

(

UINT msg,

WPARAM wParam,

LPARAM lParam

)

{

// TODO: handle any messages here, like in a normal window

// proc. note that for special keys, you'll want to override and

// implement OnSpecialKey.

//

return CallWindowProc((FARPROC)SUBCLASSWNDPROCOFCONTROL(

OBJECT_TYPE_CTLBCFCONTROLSUBWIN), m_hwnd, msg, wParam, lParam);
}

As you can see, subclassing an existing control is easy. Subclassing can significantly reduce your development effort and presents enormous potential in your ability to create powerful derivations of pre-existing controls.

Dual-Interface Controls

BaseCtl 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 on controls.

Other ActiveX Features

As with your MFC and ATL implementations, the BaseCtl framework 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 you don't go into them here. You will, however, look into their specification implementation aspects.

All of the unique information about the control and how it is created is defined in a structure called CONTROLOBJECTINFO. This structure contains the control's name, help file, flags, and so on--all of the required information for the control to be created. This structure is wrapped in four macros that are used when defining a specific type of control (see Table 11.1). Each control implementation must declare one of these macros in its header file to define the control's implementation details. Some of the ActiveX features pointed out in previous chapters are defined in this structure (refer to Chapter 6 for more information).
Table 11.1 Windowing Macros
Macro Description
DEFINE_CONTROLOBJECT This is the standard macro used for declaring a windowed control.
DEFINE_WINDOWLESSCONTROLOBJECT This is the standard macro used for declaring a windowless control.
DEFINE_CONTROLOBJECT2 This is an extended macro used for declaring a windowed control. It is similar to the standard macro but allows more control over the definition.
DEFINE_WINDOWLESSCONTROLOBJECT2 This is an extended macro used for declaring a windowless control. It is similar to the standard macro but allows more control over the definition.

Windowless Activation

Windowless activation is supported through the IOleInPlaceObjectWindowless interface and is implemented in the container. If the container doesn't support windowless activation, the control must be able to create a window for itself. Windowless activation is a request not a guarantee. Listing 11.49 shows your control definition for BCFControlControl, which is your standard windowed ActiveX control.

Listing 11.49 BCFCONTROLCTL.H--BCFControControl ActiveX Implementation

// TODO: if you have an array of verbs, then add an extern here with the name

// of it, so that you can include it in the DEFINE_CONTROLOBJECT.

// ie. extern VERBINFO m_BCFControlCustomVerbs [];

//

extern const GUID *rgBCFControlPropPages [];

DEFINE_CONTROLOBJECT(BCFControl,

&CLSID_BCFControl,

"BCFControlCtl",

CBCFControlControl::Create,

1,

&IID_IBCFControl,

"BCFControl.HLP",

&DIID_DBCFControlEvents,

OLEMISC_SETCLIENTSITEFIRST | OLEMISC_ACTIVATEWHENVISIBLE |

OLEMISC_RECOMPOSEONRESIZE | OLEMISC_CANTLINKINSIDE | OLEMISC_INSIDEOUT,

0, // no IPointerInactive policy by default

RESID_TOOLBOX_BITMAP1,

"BCFControlWndClass",

1,

rgBCFControlPropPages,

0,
NULL);

Listing 11.50 shows the control definition for BCFControlNoWinControl, a windowless control. The only difference between your windowed and windowless control is this macro. Embedded within the macro is the value indicating that this control is windowed or windowless; you just use the correct macro to get the required behavior. The DEFINE_WINDOWLESSCONTROLOBJECT macro contains one additional parameter: the highlighted TRUE value in Listing 11.50, which is used to define whether the control is 100 percent opaque. In other words, does the control draw over its entire client area, or are there some transparent parts?

Listing 11.50 BCFCONTROLNOWIN.H--BCFControlNoWinControl ActiveX Implementation

extern const GUID *rgBCFControlNoWinPropPages [];

DEFINE_WINDOWLESSCONTROLOBJECT(BCFControlNoWin,

&CLSID_BCFControlNoWin,

"BCFControlNoWinCtl",

CBCFControlNoWinControl::Create,

1,

&IID_IBCFControlNoWin,

"BCFControlNoWin.HLP",

&DIID_DBCFControlNoWinEvents,

OLEMISC_IGNOREACTIVATEWHENVISIBLE | OLEMISC_SETCLIENTSITEFIRST |

OLEMISC_ACTIVATEWHENVISIBLE | OLEMISC_RECOMPOSEONRESIZE | OLEMISC_CANTLINKINSIDE |

OLEMISC_INSIDEOUT | OLEMISC_ACTSLIKEBUTTON,

POINTERINACTIVE_ACTIVATEONENTRY | POINTERINACTIVE_DEACTIVATEONLEAVE |

POINTERINACTIVE_ACTIVATEONDRAG,

TRUE, // control is opaque

RESID_TOOLBOX_BITMAP2,

"BCFControlNoWinWndClass",

1,

rgBCFControlNoWinPropPages,

0,
NULL);

Unclipped Device Context

Unclipped device context is an MFC specific optimization. The flag results in only a single operation

if (nFlags & clipPaintDC)
dc.IntersectClipRect(rcClient);

which can be found in the COleControl::OnPaint() function. The net result of this function call is to reduce the size of the area that will be drawn to.

Flicker-Free Activation

Flicker-free activation is based on the IOleInPlaceSiteEx interface. The BaseCtl frame -work automatically attempts to find this interface, so it requires no implementation on the part of the developer. See the VC++ documentation for more information about the IOleInPlaceSiteEx interface.

Mouse Pointer Notifications When Inactive

Only the windowless control BCFControlNoWin will take advantage of mouse pointer notifications when inactive. To enable mouse pointer notifications, you must declare your control, as in Listing 11.51.

Listing 11.51 BCFCONTROLNOWIN.H--Mouse Notifications

extern const GUID *rgBCFControlNoWinPropPages [];
DEFINE_WINDOWLESSCONTROLOBJECT(BCFControlNoWin,
&CLSID_BCFControlNoWin,
"BCFControlNoWinCtl",
CBCFControlNoWinControl::Create,
1,
&IID_IBCFControlNoWin,
"BCFControlNoWin.HLP",
&DIID_DBCFControlNoWinEvents,
OLEMISC_IGNOREACTIVATEWHENVISIBLE | OLEMISC_SETCLIENTSITEFIRST |
OLEMISC_ACTIVATEWHENVISIBLE | OLEMISC_RECOMPOSEONRESIZE | OLEMISC_CANTLINKINSIDE |
OLEMISC_INSIDEOUT | OLEMISC_ACTSLIKEBUTTON,
POINTERINACTIVE_ACTIVATEONENTRY | POINTERINACTIVE_DEACTIVATEONLEAVE |
POINTERINACTIVE_ACTIVATEONDRAG,
TRUE, // control is opaque
RESID_TOOLBOX_BITMAP2,
"BCFControlNoWinWndClass",
1,
rgBCFControlNoWinPropPages,
0,
NULL);

The flags for a windowless control also differ slightly from its windowed counterpart. For your implementation, specifying OLEMISC_ACTIVATEWHENVISIBLE indicates that the control should become active as soon as it is visible. By specifying OLEMISC_IGNOREACTIVATEWHENVISIBLE, you instruct the control not to become active until some form of user action on the control takes place--that is, provided that the container supports the IPointerInactive interface. If the container does not provide the IPointerInactive interface, your control will be active the entire time it is visible.

Optimized Drawing Code

Optimized drawing is handled much the same way it is in MFC and ATL. (Refer to the section on optimized drawing at the beginning of this chapter for more information.) A parameter of the OnDraw method indicates whether the control can draw using the optimized techniques first shown in the MFC implementation. In addition, the BaseCtl implementation allows for aspect or optimized drawing. Drawing with aspects is beyond the scope of this book. If you want to implement aspects, please see the OC 96 specification included in the ActiveX SDK.

Loads Properties Asynchronously

To support asynchronous properties, a control must support the stock property ReadyState. The control is responsible for updating the property and notifying the container when it has changed. (See the section on asynchronous properties at the beginning of this chapter for more information.)

From Here...

The BaseCtl framework provides a sound platform for control development. The lack of common functionality support that is equivalent to that of MFC is probably its biggest weakness. The amount of control and flexibility over your development is probably its greatest strength. It is interesting to note that the shortcomings of the BaseCtl framework are conversely proportional to the strengths of MFC, and vice versa.

Creating a control using MFC and then porting it to the BaseCtl framework will give you a true appreciation and understanding of control and container development and architecture. It will also aid greatly in other areas of ActiveX/COM development.

As far as ATL is concerned, BaseCtl is very similar in its style of implementation. The COM interfaces are the root of your control implementation, as it should be, and not a set of all-encompassing classes (as in MFC). A large number of parallels can be drawn between ATL and BaseCtl. For those developers who are really interested in how ActiveX and COM works in a control implementation, the best choice is to take a look at the BaseCtl.

Probably the greatest limitation to using the BaseCtl is that it's considered to be an unsupported tool and is provided by its authors merely as a sample of how to do control development. You need to take this into consideration when deciding which method to use when developing your ActiveX components.