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