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.
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.
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.
. . .
// 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.
//=-------------------------------------------------------------------------=
// 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.
...
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.
. . .
//=-------------------------------------------------------------------------=
// 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.
. . .
[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.
. . .
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.
#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.
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.
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.
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
Dynamic Property Enumeration #define DECLARE_STANDARD_PERPROPERTYBROWSING() \ 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).
#include "Dispids.H" 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.
HRESULT CBCFControlControl::InternalQueryInterface(REFIID
riid, void **ppvObjOut) 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.
STDMETHODIMP CBCFControlControl::MapPropertyToPage(DISPID,
LPCLSID) 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.
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.
. . . 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.
void CBCFControlControl::BeforeDestroyWindow(void) 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.
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 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. First you need to declare your COM interfaces for supporting the IDataObject
and IEnumFORMATETC interfaces (see Listings 11.16 and 11.17).
#define DECLARE_STANDARD_DATAOBJECT() \ #define DECLARE_STANDARD_ENUMFORMATETC() \ 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.
. . . You need to update the constructor to initialize your enumerator to the beginning
of the enumeration (see Listing 11.19).
. . . Since you have added two additional COM interfaces to your control, you also need
to update your QueryInterface implementation (see Listing 11.20).
HRESULT CBCFControlControl::InternalQueryInterface(REFIID
riid, void **ppvObjOut) 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.
LRESULT CBCFControlControl::WindowProc 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.
void CBCFControlControl::CopyDataToClipboard(void) 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.
void CBCFControlControl::PrepareDataForTransfer(void) 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.
void CBCFControlControl::CopyStgMedium(LPSTGMEDIUM lpTargetStgMedium,
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.
STDMETHODIMP CBCFControlControl::GetData(LPFORMATETC
lpFormatEtc, 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.
STDMETHODIMP CBCFControlControl::Next(ULONG celt, FORMATETC_RPC_FAR
* rgelt, 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.
void CBCFControlControl::OnKeyDown(UINT nChar, UINT
nRepCnt, UINT nFlags) Now that you know how to put data on the Clipboard, take a look at how you get
data off the Clipboard. . . . 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.
void CBCFControlControl::GetDataFromClipboard(void) 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.
void CBCFControlControl::GetDataFromTransfer(IDataObject
* ipDataObj) Last you add the code that will initiate the transfer; you do this in your OnKeyDown
function (see Listing 11.31).
void CBCFControlControl::OnKeyDown(UINT nChar, UINT
nRepCnt, UINT nFlags) 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.
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.
#define DECLARE_STANDARD_DROPSOURCE() \ 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.
. . . Since you added a new interface, you also need to update your QueryInterface
function (see Listing 11.34).
HRESULT CBCFControlControl::InternalQueryInterface(REFIID
riid, void You have to update your WindowProc function to route the WM_LBUTTONDOWN
messages to your OnLButtonDown function (see Listing 11.35).
LRESULT CBCFControlControl::WindowProc(UINT msg, WPARAM
wParam, LPARAM lParam) 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.
void CBCFControlControl::OnLButtonDown(UINT nFlags,
short sHor, short sVer) 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. #define DECLARE_STANDARD_IDROPTARGET() \ 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.
. . . Since you have a COM interface, you also need to update your QueryInterface
function (see Listing 11.39).
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.
BOOL CBCFControlControl::AfterCreateWindow(void) 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.
STDMETHODIMP CBCFControlControl::DragEnter(LPDATAOBJECT
pDataObject, DWORD 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.
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.
. . . 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.
. . . 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.
void CBCFControlControl::PrepareDataForTransfer(void) 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.
void CBCFControlControl::GetDataFromTransfer(IDataObject
* ipDataObj) 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.
STDMETHODIMP CBCFControlControl::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 11.47). You can still use
the CopyStgMedium function; the only difference is which internal STGMEDIUM
structure is supplied to the function.
STDMETHODIMP CBCFControlControl::GetData(LPFORMATETC
lpFormatEtc, LPSTGMEDIUM 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.
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.
//=-------------------------------------------------------------------------= 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.
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.
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).
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.
// TODO: if you have an array of verbs, then add an
extern here with the name 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?
extern const GUID *rgBCFControlNoWinPropPages []; Unclipped device context is an MFC specific optimization. The flag results
in only a single operation if (nFlags & clipPaintDC) 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 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.
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.
extern const GUID *rgBCFControlNoWinPropPages []; 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 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.
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.)
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.
© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a
Simon and Schuster Company.Listing 11.10
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); \
Listing 11.11
#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);
Listing 11.12
{
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;
}
Listing 11.13
{
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
Listing 11.14
// **
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;
}Listing 11.15
{
// 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);
}
Adding Clipboard and Drag and Drop Support
Clipboard Support
Using Built-In Clipboard Formats
Enabling a Control as a Clipboard Source Listing 11.16
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
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 11.18
#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;
};
Listing 11.19
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
Listing 11.20
{
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;
}
Listing 11.21
(
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;
}
Listing 11.22
{
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);
}
}
Listing 11.23
{
// 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 11.24
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 11.25
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;
}
Listing 11.26
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;
}
Listing 11.27
{
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));
}
}
Enabling a Control as a Clipboard Target Listing 11.28
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;
. . .
Listing 11.29
{
// 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 11.30
{
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);
}
Listing 11.31
{
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
. . .Adding Drag and Drop Support
Using Built-In Drag and Drop Formats
Enabling a Control as a Drag and Drop Source Listing 11.32
STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD dwKeyState); \
STDMETHOD(GiveFeedback)(DWORD dwEffect);Listing 11.33
#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;
}; . . .
Listing 11.34
**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;
}
Listing 11.35
{
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;
}
Listing 11.36
{
// 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;
}
Enabling a Control as a Drag and Drop Target Listing 11.37
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 11.38
#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.
// . . .
Listing 11.39
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;
}
Listing 11.40
{
// 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);
}
Listing 11.41
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;
}
Custom Clipboard and Drag and Drop Formats
Listing 11.42
private:
FORMATETC sTextFormatEtc;
STGMEDIUM sTextStgMedium;
// custom format storage variables
UINT m_uiCustomFormat;
FORMATETC sCustomFormatEtc;
STGMEDIUM sCustomStgMedium;
};
Listing 11.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;
}
#pragma warning(default:4355) // using `this' in constructor
Listing 11.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_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;
}
}
Listing 11.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_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);
}
Listing 11.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 11.47 BCFCONTROLCTL.CPP--IEnumFORMATETC::GetData
Update
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;
}
Subclassing Existing Windows Controls
Listing 11.48
// 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);
}
Dual-Interface Controls
Other ActiveX Features
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
Listing 11.49
// 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
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
dc.IntersectClipRect(rcClient);Flicker-Free Activation
Mouse Pointer Notifications When Inactive
Listing 11.51
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);
Optimized Drawing Code
Loads Properties Asynchronously
From Here...