This chapter expands upon the information in Chapter 6 about creating a basic MFC ActiveX control. In addition to the features that you are familiar with, such as Clipboard and Drag and Drop support, you will learn how to implement asynchronous properties and optimized drawings, which are the result of the adoption of OC 96 specification.
Chapter 6 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 file or a bitmap, and are loaded as a background process so as not to interfere with the normal processing of the control and the container. This statement can be somewhat misleading. Asynchronous refers only to the call to load the data; it does not refer to the actual loading. For example, a control uses a bitmap as its background and has defined it 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.
Except for a few changes, asynchronous properties are added in the same fashion as any other property. In Chapter 6, when you initially create your control, you have the opportunity to define some ActiveX features, one being Loads Properties Asynchronously. Choosing this option adds some code to your application that normally would not be implemented.
First, the stock property ReadyState was added to your control. This property is used to notify the container of the state that the control is in while loading its properties. The stock event ReadyStateChange, which is used to notify the container that the ReadyState of the control has changed, was also added. The last thing that was added was the initialization of the member variable m_lReadyState to READYSTATE_LOADING in the controls constructor (see Listing 7.1).
CMFCControlWinCtrl::CMFCControlWinCtrl()
{
InitializeIIDs(&IID_DMFCControlWin, &IID_DMFCControlWinEvents);
m_lReadyState = READYSTATE_LOADING;
// TODO: Call InternalSetReadyState when the readystate changes.
// set the alignment to the default of left
m_lAlignment = EALIGN_LEFT;
}
This is the extent of the work that is done for you at the time your project is created.
The control must have an entry point for OLE to communicate with when notifying the control when and how much data is available. This is accomplished through the CDataPathProperty or the CCachedDataPathProperty class.
CDataPathProperty is used for data that typically arrives in a continuous fashion, such as stock market data. CCachedDataPathProperty is used for data that is retrieved once and then stored or cached, such as a file. You must implement a class in your control that is inherited from one of these classes. To add a new class, you use the ClassWizard.
For the purposes of this chapter and the sample application, you will implement an asynchronous property that reads string data from a file and outputs the data as the caption of the control.
Open the ClassWizard, click the Add Class button on any one of the tab
pages, and select the New menu item. In the New Class dialog (see fig. 7.1), enter
the Name CMyDataPath, select a Base class of CCachedDataPathProperty,
and click the OK button to add the new class.
FIG. 7.1
Add a new CCachedDataPath Property class with the ClassWizard.
Your control must override the OnDataAvailable function within the CMyDataPath class in order to receive data as it becomes available. From the open ClassWizard dialog, select the Message Maps tab and locate the OnDataAvailable message within the Messa_ges list box. Double-click the OnDataAvailable message to add the method to your class. Click the OK button to close the ClassWizard.
The first step is to add the cstrMyBuffer member variable to the CMyDataPath class (see Listing 7.2). The member variable, cstrMyBuffer, is used to store all of the data as it is passed to the OnDataAvailable function.
. . .
// Implementation
protected:
CString cstrMyBuffer;
};
The next step is to update the control class, CMFCControlWinCtrl, to include the header file of the CMyDataPath class and also to add a new member variable, oMyDataPath (see Listing 7.3). The CMyDataPath class also needs access to the function CaptionMethod in the control so that the data can be placed in the control when it is all available. Since the CaptionMethod is a protected function, it is necessary to allow the CMyDataPath class access to the function using the C++ friend declaration.
. . .
#include "alignmentenums.h"
#include "mydatapath.h"
/////////////////////////////////////////////////////////////////////////////
// CMFCControlWinCtrl : See MFCControlWinCtl.cpp for implementation.
. . .
friend class CMyDataPath;
CMyDataPath oMyDataPath;
};
. . .
The next step is to add the code to the OnDataAvailable function in the CMyDataPath class to deal with the data as it is sent to the control. The OnDataAvailable method is straightforward (see Listing 7.4). It has two properties, dwSize and bscfFlag. DwSize is the number of bytes of data that are currently available in this call to the function. BscfFlag is a set of flags indicating the current operation taking place (see Table 7.1).
Notification Message | Description |
BSCF_FIRSTDATANOTIFICATION | This message is sent the first time that the OnDataAvailable function is called. It is important to note that this message can be sent along with the BSCF_LASTDATANOTIFICATION message. |
BSCF_INTERMEDIATEDATANOTIFICATION | This message is sent while the OLE is still loading data. |
BSCF_LASTDATANOTIFICATION | This message is sent when a control has been given all of the data that is available to the property. It is important to note that this message can be sent along with the BSCF_FIRSTDATANOTIFICATION message. |
The OnDataAvailable implementation first clears the string buffer if this is the first call to the function. Next it creates a buffer of the appropriate size to receive the data from the Read function. If the data was successfully read, the data is added to the member variable cstrMyBuffer. Finally if this is the last call to the OnDataAvailable function, the CaptionMethod is called supplying the new data and an empty variant parameter for the alignment. The last thing you must do is change the state of the control to READYSTATE_COMPLETE, which indicates that all of the asynchronous properties have been loaded.
void CMyDataPath::OnDataAvailable(DWORD dwSize, DWORD
bscfFlag)
{
// if this is the first notification
if(bscfFlag & BSCF_FIRSTDATANOTIFICATION)
// clear the string buffer
cstrMyBuffer.Empty();
CString cstrTempBuffer;
// get a temp buffer
LPTSTR lptstrTempBuffer = cstrTempBuffer.GetBuffer(dwSize);
// read the data to a temp buffer
UINT uiBytesRead = this->Read(lptstrTempBuffer, dwSize);
// if we read in any data
if(uiBytesRead)
{
// store the data
cstrTempBuffer.ReleaseBuffer(uiBytesRead);
cstrMyBuffer += cstrTempBuffer;
}
// if this is our last notification
if(bscfFlag & BSCF_LASTDATANOTIFICATION)
{
VARIANT varAlignment;
::VariantInit(&varAlignment);
varAlignment.vt = VT_EMPTY;
((CMFCControlWinCtrl *) this->GetControl())->CaptionMethod(cstrMyBuffer,
varAlignment);
this->GetControl()->InternalSetReadyState(READYSTATE_COMPLETE);
}
}
Next you need to add an OLE property to store the actual location of the data that is to be loaded asynchronously. Open the ClassWizard, select the Automation tab, select the CMFCControlWinCtrl class, and click the Add Property button. In the Add Property dialog, enter the External name TextData, select the Type BSTR and an Implementation of Get/Set methods. Do not use the OLE_DATAPATH type for this property, as it doesn't work; the type must be a BSTR. Use of the OLE_DATAPATH and its related problems is a known bug with Microsoft and MFC. Click the OK button to close the dialog and add the property to the class. Double-click the TextData entry in the External names list box to close the ClassWizard and open the source file.
To complete your implementation, you need to add some code to the CMFCControlWinCtrl class source file. First update the class constructor to set the control class in the oMyDataPath object (see Listing 7.5). Doing this is absolutely necessary for the DoPropExchange function to work correctly when loading the data path property.
CMFCControlWinCtrl::CMFCControlWinCtrl()
{
InitializeIIDs(&IID_DMFCControlWin, &IID_DMFCControlWinEvents);
// TODO: Call InternalSetReadyState when the readystate changes.
m_lReadyState = READYSTATE_LOADING;
// set the alignment to the default of left
m_lAlignment = EALIGN_LEFT;
// don't forget this - DoPropExchange won't work without it
oMyDataPath.SetControl(this);
}
Next add the oMyDataPath property persistence to the DoPropExchange function (see Listing 7.6). Remember that without the call to SetControl in the constructor, this code will not function correctly.
void CMFCControlWinCtrl::DoPropExchange(CPropExchange*
pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
// TODO: Call PX_ functions for each persistent custom property.
// if we are loading the properties
if(pPX->IsLoading())
{
PX_Long(pPX, _T("Alignment"), m_lAlignment, EALIGN_LEFT);
PX_String(pPX, _T("CaptionProp"), m_cstrCaption, _T(""));
PX_DataPath(pPX, _T("TextData"), oMyDataPath);
}
// if we are saving the properties
if(!pPX->IsLoading())
{
PX_Long(pPX, _T("Alignment"), m_lAlignment, EALIGN_LEFT);
PX_String(pPX, _T("CaptionProp"), m_cstrCaption, _T(""));
PX_DataPath(pPX, _T("TextData"), oMyDataPath);
}
}
Finally you add the code to the GetTextData and SetTextData methods (see Listing 7.7). The GetTextData function simply returns a UNICODE version of the property value. The SetTextData implementation calls the Load function to load the data pointed to by the lpszNewValue parameter. After the Load function returns, the SetModifiedFlag and InvalidateControl functions are called to update the control and the data with the new information.
BSTR CMFCControlWinCtrl::GetTextData()
{
// retrieve the path and allocate a BSTR from it
return (oMyDataPath.GetPath()).AllocSysString();
}
void CMFCControlWinCtrl::SetTextData(LPCTSTR lpszNewValue)
{
// load the DataPath variable based on the new information
this->Load(lpszNewValue, oMyDataPath);
// update the property
this->SetModifiedFlag();
// redraw the control
this->InvalidateControl();
}
Again, it is very important to understand that asynchronous properties are not a guarantee of improved performance in a general sense. They are merely an option available to you for creating Internet controls that are more responsive across slower network connections.
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. This is a good candidate for both a static set, say English and German, and a dynamic set, say for all the languages on a particular machine.
Adding property enumeration greatly enhances the look and feel of your control. This addition gives the user all the options that are possible, with a simple click of a mouse. As a result the user doesn't have to rather than trying to search through documentation or, worse yet, trying to guess the acceptable values.
When editing the value of an enumerated property within a property browser, note that development tools such as Visual Basic display all the values of the enumeration using a string representation rather than just the actual value that the control can accept. For a Boolean data type, the strings used are TRUE and FALSE, representing -1 or 0, respectively.
Two approaches can be taken when creating an enumeration for a property: You can
use a static approach, with an enumeration defined in the control's ODL file, or
a dynamic approach, with enumeration code implemented in the control itself.
Static Property Enumeration
Remember to generate a new UUID for the enumeration with the GUIDGEN.EXE application included with VC++.
The helpstring is what the user will see within the property browser of your development environment. If you leave off the helpstring, the actual numeric value will appear instead.
The last thing you did was to change the data type of the Alignment property
from long to EALIGNMENT. This is required if the property is to
display the enumerated values within the property browser.
NOTE: ODL is very flexible in that it allows much the same style of data type declaration and use as that of C or C++. Any data type that can be declared in ODL can also be referenced and used within the same and other ODL files, including interface declarations. In addition, other type libraries can be imported into an ODL file to provide access to other user-defined data types. In the case of the sample control, two types of libraries, STDOLE_TLB and STDTYPE_TLB, are included for the standard OLE interface and data type declarations.
The ODL documentation can be a little difficult to decipher, but we recommend that you at least review the documentation. It is well worth the effort just to see what you can and cannot do with your type library and how it will affect the container of your control or component.
It is absolutely critical that the developer of containers adhere to the standards established in the ODL documentation. You will find that some of the aspects of type library creation and use are based on cooperation and trust and that component developers depend on that.
typedef
[ uuid(7F369B90-380D-11d0-BCB6-0020AFD6738C) ]
enum tagAlignmentEnum
{
[helpstring("Left Justify")] EALIGN_LEFT = 0,
[helpstring("Right Justify")] EALIGN_RIGHT = 1,
[helpstring("Center")] EALIGN_CENTER = 2,
}EALIGNMENT;
// Primary dispatch interface for CMFCControlWinCtrl
[ uuid(14DD5C04-60DE-11D0-BEE9-00400538977D),
helpstring("Dispatch interface for MFCControlWin Control"), hidden
]
dispinterface _DMFCControlWin
{
properties:
// NOTE - ClassWizard will maintain property information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_PROP(CMFCControlWinCtrl)
[id(DISPID_READYSTATE), readonly] long ReadyState;
[id(1)] EALIGNMENT Alignment;
[id(DISPID_BACKCOLOR), bindable, requestedit] OLE_COLOR BackColor;
[id(2)] BSTR TextData;
//}}AFX_ODL_PROP
methods:
. . .
Dynamic Property Enumeration class CMFCControlWinCtrl : public COleControl OnGetPredefinedStrings is called by the container application when it
needs to load the strings and cookies for the enumeration (see Listing 7.10). The
string is used as the text representation of the enumerated value to make it more
meaningful to the user. The cookie is a 32-bit value that can be used in any way
that is appropriate to identify the value associated with the display string of the
enumeration. In your case, you are going to store the actual value that the string
represents, but it could just as easily have been a pointer or index into some form
of storage.
BOOL CMFCControlWinCtrl::OnGetPredefinedStrings(DISPID
dispid, CStringArray* pStringArray, CDWordArray* pCookieArray) The OnGetPredefinedStrings function is called for every one of the properties
in your control, so your code will have to check the dispid that is passed
to the function to make sure that the correct enumeration is going to the correct
property. The dispids of the control properties can be found in the class
declaration of your control and are maintained automatically by the ClassWizard (see
Listing 7.11).
. . . When the user selects one of the enumerated values in the property browser, the
control's OnGetPredefinedValue function is fired (see Listing 7.12). This
allows the container to retrieve the actual value that should be stored in the property
in place of the string representation. The function is passed the dispid
identifying the property that is being changed and the cookie value that was assigned
to the string representation in the OnGetPredefinedStrings function.
BOOL CMFCControlWinCtrl::OnGetPredefinedValue(DISPID
dispid, DWORD dwCookie, VARIANT FAR* lpvarOut) OnGetDisplayString (see Listing 7.13) is the last method to be called
and is required if the programmer wants the text representation to appear next to
the property in the property browser when the property is not being edited. The function
is passed a dispid, again reflecting the current property, and a CString
object reference. The control should place the string, as reflected by the current
state of the property, into the CString object and exit the function.
BOOL CMFCControlWinCtrl::OnGetDisplayString( DISPID
dispid, CString& strValue ) Optimized drawing allows you to create drawing objects, such as pens or brushes,
and rather than removing them when you are finished drawing, you can store them in
your control member variables and use them again 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: 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 will be 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 appropriate. For MFC, the scope of optimized drawing is very narrow compared to the OC 96 specification,
but it will, nonetheless, result in performance improvements if taken advantage of.
The OC 96 specification further breaks optimized drawing into what is known as aspects,
but MFC is not designed to allow that kind of drawing. For more information on aspect
drawing, please see the OLE Control 96 Specification that ships with the ActiveX
SDK. . . . The Clipboard is an area of the Windows operating system that acts like
a bulletin board for data that can be shared between applications. By means of a
series of keystrokes or menu options, a user can copy data to the Clipboard from
one application and paste the data from the Clipboard into another application. Drag and Drop provides the user with the ability to transfer data between
two applications by means of a mouse only. The user selects the data to transfer,
holds down a mouse button on the selected data, drags the data to the location where
is it to be added, and releases the mouse button, thus dropping the data into the
new location. Drag and Drop support is similar in its implementation to Clipboard
support, and you will take full advantage of it in your implementa Adding Clipboard and Drag and Drop support to a control can make even the simplest
implementations appear more professional and well-rounded.
The first step is deciding which keystrokes will be used to initiate the cut,
copy, or paste operation. Fortunately, the Windows operating system already has a
number of standards in this area. You will use Ctrl+X or Shift+Delete for Cut, Ctrl+C
or Ctrl+Insert for Copy, and Ctrl+V or Shift+Insert for Paste. Open the ClassWizard and select the Message Maps tab for the CMFCControlWinCtrl
class. Double-click WM_KEYDOWN in the list of messages to add the OnKeyDown
function to your message map (see fig. 7.2). The OnKeyDown function is where
you are going to add your code to look for the keystroke combinations that will initiate
the Clipboard transfers. Double-click the OnKeyDown member function to close
the ClassWizard and open the source file for editing. Listing 7.15 shows the code that is added to OnKeyDown to support the
keystroke combinations listed in the preceding paragraph. The implementation is simple;
based on the particular state of the Ctrl or Shift keys and the correct keystroke,
the function either copies the data to or copies the data from the Clipboard.
void CMFCControlWinCtrl::OnKeyDown(UINT nChar, UINT
nRepCnt, UINT nFlags) In addition to the code that you added to trap the keystrokes, you also need to
add four methods, which you will examine in detail in the next section, for dealing
with the Clipboard transfers. CopyDataToClipboard will, as the name implies, get the data from the
control, and using the helper function, PrepareDataForTransfer, will package
the data and put it on the Clipboard. GetDataFromClipboard will open the Clipboard and look for data formats
that the control understands. Upon finding a suitable format, GetDataFromClipboard
will use the helper function GetDataFromTransfer to store the data in the
control. The fact that the data transfer functions have been separated into two separate
methods for each type of transfer, to and from the Clipboard, and then further broken
down in each type of transfer into two separate steps will aid you when you enable
the control for Drag and Drop support. This is because the basic data transfer mechanism
is the same between the Clipboard and Drag and Drop and will allow you to rely on
a large portion of shared code for each implementation. CF_TEXT The first implementation of Clipboard data transfer will rely on the CF_TEXT
format. This is a general format for transferring non-UNICODE text data. There are
two aspects to using the Clipboard: being a Clipboard source and being a Clipboard
target. Being a Clipboard source refers to an application's capability to copy data
to the Clipboard. Being a Clipboard target refers to an application's capability
to copy data from the Clipboard. You will first learn how to enable a control
as a Clipboard source and then how to enable a control as a Clipboard target. . . . CopyDataToClipboard uses the COleDataSource class provided by
MFC to connect to the Clipboard (see Listing 7.17). By calling the GetClipboardOwner
function, your implementation first checks to see whether you already have an object
on the Clipboard. GetClipboardOwner returns a pointer to a COleDataSource
object if the Clipboard contains a COleDataSource object that you had previously
set to the Clipboard using the SetClipboard function. If you are not the
owner of the Clipboard, the method returns NULL. If you didn't get a reference
to a COleDataSource object, you need to create one. Next you call the general
method PrepareDataForTransfer passing in your COleDataSource object,
which will copy the data from the control to the COleDataSource object.
The final thing you do is put the COleDataSource object on the Clipboard,
but only if you weren't the owner of the Clipboard at the time the transfer occurred.
Setting the Clipboard again with the same object will result in an error.
void CMFCControlWinCtrl::CopyDataToClipboard(void) PrepareDataForTransfer is used to create the necessary memory structures
and to prepare the COleDataSource object that will be used in the data transfer
(see Listing 7.18). For your data transfer, you are going to place the Caption on
the Clipboard using the Clipboard format CF_TEXT. In order to put the text
data on the Clipboard, you have to create a global memory object and copy the data
to it. After you create the global object, you store it in the COleDataSource
object using the CacheGlobalData function. Don't worry about cleaning up
any data that was previously set in the COleDataSource object; the CacheGlobalData
function will do that for you.
void CMFCControlWinCtrl::PrepareDataForTransfer(COleDataSource
* Enabling a Control as a Clipboard Target The opposite of being a Clipboard
source is being a Clipboard target. First you need to update the CMFCControlWinCtrl
class to include two new helper functions (see Listing 7.19).
. . . Getting data from the Clipboard is almost as simple as it is to put the data on
the Clipboard in the first place. GetDataFromClipboard uses the COleDataObject
MFC class to attach to the Clipboard and, if successful, passes the object to the
GetDataFromTransfer helper function (see Listing 7.20).
void CMFCControlWinCtrl::GetDataFromClipboard(void) GetDataFromTansfer enumerates all of the available formats in the COleDataObject
object using BeginEnumFormats (see Listing 7.21). To optimize the search
a little bit, you should first see whether the Clipboard even contains any data that
you can use. In this case, you are looking for the CF_TEXT format. The implementation
contains a simple while loop that will execute as many times as there are
formats in the COleDataObject object. When you locate a CF_TEXT
format, you retrieve its global memory object with the function GetGlobalData
and copy the data that it points to into the control. Do not destroy the data that
was retrieved with this method--you are not its owner; the COleDataObject
is.
BOOL CMFCControlWinCtrl::GetDataFromTransfer(COleDataObject
* opOleDataObject) Custom Clipboard Formats The fundamentals of Drag and Drop support are very similar to Clipboard support
and rely on the same MFC classes, COleDataSource and COleDataObject,
for their implementation. Since you have broken your code into separate functions--that
is, the Clipboard specific code is isolated from the basic data transfer functions--most
of your implementation is complete. To open the source file and add your code, double-click the OnLButtonDown
function in the Member functions list box. The OnLButtonDown implementation is similar to the Clipboard method CopyDataToClipboard
(see Listing 7.22). Again, you are using a COleDataSource object and loading
it with your data with the PrepareDataForTransfer call. To actually initiate the Drag and Drop operation, you call DoDragDrop
specifying the constant DROPEFFECT_COPY, which indicates that this is a
copy operation. The constant DROPEFFECT_COPY is used for two purposes. The
first, and most obvious to the user, is that the mouse cursor will change to indicate
that a copy drag is in progress. The second is to inform the drop target as to the
intent of the drop operation.
void CMFCControlWinCtrl::OnLButtonDown(UINT nFlags,
CPoint point) Now that the control has been enabled as a Drag and Drop source, it only makes
sense to enable it as a Drag and Drop target. The class CMyOleDropTarget needs to be a friend of the CMFCControlWinCtrl
class so that the CMyOleDropTarget class may call the general GetDataFromTransfer
function to store the data in the control.
class CMyOleDropTarget: public COleDropTarget As we stated earlier, an application must register itself with the operating system
in order to be a valid Drag and Drop target. Register the control as a Drag and Drop
target using the function, Register, in your OnCreate method for
the control. To add the OnCreate function to the class, you use the ClassWizard.
Open the ClassWizard, and select the Message Maps tab. Select the class CMFCControlWinCtrl,
and double-click the WM_CREATE message to add the OnCreate function
to your class (see fig. 7.4). To add your code, open the source file by double-clicking the OnCreate
function in the Member functions list box. Register is the only call that is required to enable your control as
a drop target (see Listing 7.24). The member variable opMFCControlWinCtrl
is set with a reference to the control so you can call the general method GetDataFromTransfer
to put the data into your control; this is specific to your implementation and not
a requirement for Drag and Drop target support.
int CMFCControlWinCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
The OnDragOver function is used to instruct Windows that your control
is a valid or invalid drop target for the current drag operation (see Listing 7.25).
The implementation of OnDragOver first looks to see whether the mouse button
is being held down. If it is, OnDragOver then looks for the availability
of the CF_TEXT format. If it finds the CF_TEXT format, the OnDragOver
function returns the constant DROPEFFECT_COPY, indicating to Windows that
the control is a valid drop target for the particular drop operation that is currently
in effect. Otherwise, OnDragOver returns DROPEFFECT_NONE, indicating
that a drop should not be allowed.
DROPEFFECT CMFCControlWinCtrl::CMyOleDropTarget::OnDragOver(CWnd*
pWnd, OnDrop is where you call GetDataFromTransfer, the same function
that you created for your Clipboard operations, to copy the data from the drop source
into your control (see Listing 7.26).
BOOL CMFCControlWinCtrl::CMyOleDropTarget::OnDrop(CWnd*
pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) Custom Drag and Drop Formats Custom Clipboard and Drag and Drop formats are a way for applications to trade
information on a more intimate basis. The data transferred can be of any type and
structure that the applications can create and use. When using custom Clipboard formats,
all applications that will use the format must first call RegisterClipboardFormat
passing in the name of the format. Before you add the code to register your custom format, add a member variable,
m_uiCustomFormat, to your class definition (see Listing 7.27). You will
use the value that this member variable holds later in your implementation to determine
whether a valid format has been registered.
. . . Now add the custom format registration code to the constructor of the class CMFCControlWinCtrl
(see Listing 7.28). The RegisterClipboardFormat function, if it succeeds, will return the
ID of the newly registered or already existing (in the case where the format is already
registered) custom Clipboard format. It is important that all applications that are
going to use the custom format call this method.
CMFCControlWinCtrl::CMFCControlWinCtrl() After the custom format has been registered, it is a simple matter to add the
code to your implementation that stores or retrieves the data from the COleDataSource
and COleDataObject objects. Using the code that was created for your standard
data transfers as a base, you will now add the custom data transfer code. PrepareDataForTransfer (see Listing 7.29) differs only slightly from
its original implementation. For the custom data transfer, you are going to send
the m_lAlignment value in addition to the caption. You can use any data
type or structure, including user-defined data types, for your custom format. The
only requirement is that both the source and the target applications understand the
format being transferred.
void CMFCControlWinCtrl::PrepareDataForTransfer(COleDataSource
* NOTE: Any number of data formats can be transferred at one time for a single
data source object. It is up to the receiving application to deal with this possibility
and retrieve the data correctly. The MFC implementation of the COleDataSource
class does not allow duplicate formats to be available in the same object, but that's
not to say that this can't happen. Non-MFC applications have complete freedom to
implement data sources any way they see fit. Remember yours may not be the only data
being transferred.
BOOL CMFCControlWinCtrl::GetDataFromTransfer(COleDataObject
* opOleDataObject) Because both the Clipboard support and the Drag and Drop support rely on the same
data transfer routines to exchange data, you have the added benefit of supporting
the custom data formats for each, while having to maintain only two functions, instead
of four.
Since the early days of Windows programming, programmers have enjoyed the option
of using the default behavior of existing Windows controls and extending them slightly
to create new and more powerful controls. This technique of creating Windows controls
is referred to as subclassing (and there is also superclassing, depending on the
technique you use). The same is still true for ActiveX controls. When you created the original application code with the MFC AppWizard, you created
a total of three controls. One of those controls, CMFCControlSubWin, subclassed
a Windows BUTTON control. Listing 7.31 contains all of the additional code that is required of an ActiveX
control to subclass a control, which, by the way, was generated for you automatically
by the MFC AppWizard when you created the project.
///////////////////////////////////////////////////////////////////////////// OnDraw is where you call DoSuperClassPaint to instruct the subclassed
control to paint itself. Calling DoSuperClassPaint will work only for those
controls that accept a device context handle (HDC) as the WPARAM
of their WM_PAINT message. PreCreateWindow is where the most important action of the whole subclassing
process occurs; you identify, by name, the control to subclass. The name used can
be any valid Windows control, including user-defined controls. As long as the control
is a registered, valid Windows control, you can subclass it. It's pretty obvious what IsSubclassedControl is used for. MFC queries
the function to determine whether it subclasses a Windows control. OnOcmCommand is where you get all of your reflected windows messages.
Within this function, you can respond to messages like button clicks and edit change
notifications. Microsoft has created sample code and plenty of documentation regarding control
subclassing, so we won't get into any implementation details here. NOTE: Note that a subclassed control may not behave exactly as its original
implementation does. In the case of subclassing a list box, the font that the control
uses may not match that of the container. Getting the subclassed list box to use
the correct font can be a little difficult and may require sending messages directly
to the subclassed window rather than allowing the MFC base implementation to define
the font. At this time, none of the control containers can, or will, take advantage of dual-interfaces
implemented in a control, but that is not to say that they won't in the future. The
addition of dual-interface support to your control is exactly the same as adding
dual-interface to ActiveX Automation Servers. It will add only a little more code
to your application and should result in significant performance improvement for
the control if and when containers are built to support dual-interface. Microsoft strongly suggests that controls be created with dual-interface support,
in spite of the fact that, at the time of the writing of this book, none of Microsoft's
containers can use them.
Finally, the section that you all have been waiting for: Advanced ActiveX features.
Don't stand up and jump just yet. Most of the features require little code, if any
at all, and really aren't intended for anything more than trimming a few more precious
milliseconds from your load times and runtimes. When the AppWizard creates the basic source files for your control, it adds a
member function GetControlFlags to your class header and source files. GetControlFlags
is where the control informs MFC about the kind of ActiveX support it has by returning
a set of flags. The documentation for GetControlFlags lists all of the valid
flags that can be returned from this function, thereby indicating the control's level
of ActiveX support. You covered the features, optimized drawing and asynchronous properties, earlier
in the chapter. Now take a look at the implementation specifics for the rest of the
features.
Setting this flag will allow a control to be created without the burden of creating
a window, significantly improving the control's start-up time. The implementation of windowless controls versus windowed controls, for the most
part, will remain unchanged since most of the windowless details are hidden from
the control programmer. For your own implementation of the CMFCControlWinCtrl class, nothing
that you did in your implementation would prevent you from using this feature. The
main restriction when writing a windowless control is that you cannot access the
window handle directly without first checking to see whether it is valid. If your
control implementation never uses the window handle, you should be fine. To prove the point regarding the implementation of windowed and windowless controls,
the CMFCControlNoWin class's implementation of OnDraw contains
the same code as your windowed implementation. One thing to note though is that the container is the deciding factor when it
comes to windowed versus windowless support. A window will be created for the control
automatically if the container does not have windowless support. This is a requirement
for all controls, regardless of the tool used to create them. See the VC++ books online article "OLE Controls: Optimization" for the
specific implementation details regarding message maps and routing for windowless
controls.
Flicker-free activation simply says that the control will not receive a notification
to draw its UI when it transitions between an active and inactive state, and vice
versa. The only requirement of a control with this feature is that the control's
UI remains the same regardless of the state of the control.
Unclipped device context is a commitment more than a feature. When this flag is
set, the control is telling the container that it will not draw outside of its client
area. Setting the unclipped device context flag will improve performance by eliminating
unnecessary clipping tests. Unclipped device context cannot be used with the windowless
controls.
Mouse pointer notifications when inactive allows the control to receive mouse
notification messages, even though the window may not be active. Mouse pointer notifications
when inactive is used for those users who turned off the feature "Activate when
visible" when first creating their control project with the AppWizard. The only special circumstance to be concerned with is the case of Drag and Drop
operations, which require an activated window. The programmer of the control must
override the control's GetActivationPolicy function to instruct the container
how to activate the control when it becomes the target of a Drag and Drop operation. See the VC++ books online article "OLE Controls: Optimization" for the
specific implementation details regarding this feature.
In this chapter, we demonstrated fairly simple techniques for creating unique
and interesting controls and control features. We built upon simple concepts and
methods that, in turn, resulted in a control that is far greater than the sum of
its parts. A little bit of work and forethought can go a long way when using MFC. MFC provides a large and robust framework for creating ActiveX controls. The ease
of use and support provided by the AppWizard, ClassWizard, and the VC++ IDE make
MFC unbeatable for rapid control development. Unfortunately, those very same features
and functionality make MFC a hard choice when building small, fast controls for use
over the Internet or in applications where performance is an issue. DLL load times
and unnecessary code overhead can make MFC unreasonable to use for simple, lightweight
controls. On the other hand, MFC hides a large number of the details surrounding
control implementation and leaves you free to focus on the control and its specific
implementation details. All of these things must be considered when choosing MFC
as a control development framework. The next four chapters examine in detail how to create a similar control implementation
using ATL and BaseCtl.
© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a
Simon and Schuster Company.Listing 7.9 MFCCONTROLWINCTL.H--Dynamic Property Enumeration
Function Prototypes Added to the CMFCControlWinCtrl Class
{
DECLARE_DYNCREATE(CMFCControlWinCtrl)
// Constructor
public:
CMFCControlWinCtrl();
BOOL OnGetPredefinedStrings(DISPID dispid, CStringArray* pStringArray,
CDWordArray* pCookieArray);
BOOL OnGetPredefinedValue(DISPID dispid, DWORD dwCookie, VARIANT FAR*
lpvarOut);
BOOL OnGetDisplayString( DISPID dispid, CString& strValue );
// Overrides
. . .
Listing 7.10 MFCCONTROLWINCTL.CPP--OnGetPredefinedStrings
Implementation
{
BOOL bResult = FALSE;
// which property is it
switch(dispid)
{
case dispidAlignment:
{
// add the string to the array
pStringArray->Add(_T("Left Justify"));
// add the value to the array
pCookieArray->Add(EALIGN_LEFT);
// add the string to the array
pStringArray->Add(_T("Center"));
// add the value to the array
pCookieArray->Add(EALIGN_CENTER);
// add the string to the array
pStringArray->Add(_T("Right Justify"));
// add the value to the array
pCookieArray->Add(EALIGN_RIGHT);
// set the return value
bResult = TRUE;
}
break;
}
return bResult;
}
Listing 7.11 MFCCONTROLWINCTL.H--Control dispid Enumeration
// Dispatch and event IDs
public:
enum {
//{{AFX_DISP_ID(CMFCControlWinCtrl)
dispidAlignment = 1L,
dispidTextData = 2L,
dispidCaptionMethod = 3L,
dispidCaptionProp = 4L,
eventidChange = 1L,
//}}AFX_DISP_ID
};
. . .
Listing 7.12 MFCCONTROLWINCTL.CPP-- OnGetPredefinedValue
Implementation
{
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;
}
Listing 7.13 MFCCONTROLWINCTL.CPP--OnGetDisplayString
Implementation
{
BOOL bResult = FALSE;
// which property is it
switch(dispid)
{
case dispidAlignment:
{
switch(m_lAlignment)
{
case EALIGN_LEFT:
strValue = _T("Left Justify");
break;
case EALIGN_CENTER:
strValue = _T("Center");
break;
case EALIGN_RIGHT:
strValue = _T("Right Justify");
break;
}
// set the return value
bResult = TRUE;
}
break;
}
return bResult;
}
Drawing the Control
Optimized DrawingListing 7.14 MFCCONTROWINCTL.CPP--Optimized OnDraw Function
// set the old font back
pdc->SelectObject(pOldFont);
// **
// ****** Get the text font ******
// The container does not support optimized drawing.
if(!IsOptimizedDraw())
{
// select the old brush back
pdc->SelectObject(pOldBrush);
}
}
Adding Clipboard and Drag and Drop Support
Clipboard Support
FIG. 7.2
Add the OnKeyDown message map with the ClassWizard.Listing 7.15 MFCCONTROLWINCTL.CPP--OnKeyDown Implementation
{
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)
{
case 0x56: // `V' // PASTE
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;
}
case 0x43: // `C' // COPY or PASTE
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' // CUT
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
m_cstrCaption.Empty();
// 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
COleControl::OnKeyDown(nChar, nRepCnt, nFlags);
}
}
Using Built-In Clipboard Formats
CF_BITMAP
CF_METAFILEPICT
CF_SYLK CF_DIF
CF_TIFF CF_OEMTEXT
CF_DIB CF_PALETTE
CF_PENDATA
CF_RIFF
CF_WAVE
CF_UNICODETEXT
CF_ENHMETAFILE
CF_HDROP
CF_LOCALE
CF_MAX
CF_OWNERDISPLAY
CF_DSPTEXT
CF_DSPBITMAP
CF_DSPMETAFILEPICT
CF_DSPENHMETAFILE
CF_GDIOBJFIRST
CF_GDIOBJLAST
Enabling a Control as a Clipboard Source Listing 7.16 MFCCONTROLWINCTL.H--Clipboard Source Support
Helper Function Prototypes
void CopyDataToClipboard(void);
void PrepareDataForTransfer(COleDataSource * opOleDataSource);
};
. . .
Listing 7.17 MFCCONTROLWINCTL.CPP--CopyDataToClipboard
Implementation
{
// get the clipboard if we are the owner
COleDataSource * opOleDataSource = COleDataSource::GetClipboardOwner();
BOOL bSetClipboard = FALSE;
// if we didn't get back a pointer
if(opOleDataSource == NULL)
{
// if this is a new clipboard object
bSetClipboard = TRUE;
// get a new data source object
opOleDataSource = new COleDataSource;
}
// call the common data preparation function
this->PrepareDataForTransfer(opOleDataSource);
// did we get a new clipboard object?
if(bSetClipboard)
// pass the data to the clipboard
opOleDataSource->SetClipboard();
}
Listing 7.18 MFCCONTROLWINCTL.CPP-- PrepareDataForTransfer
Implementation
opOleDataSource)
{
// get the length of the data to copy
long lLength = m_cstrCaption.GetLength() + 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_cstrCaption.GetAt(lCount);
// null terminate the string
lpTempBuffer[lCount] = `\0';
// unlock the memory
::GlobalUnlock(hGlobal);
// cache the data
opOleDataSource->CacheGlobalData(CF_TEXT, hGlobal);
}
Listing 7.19 MFCCONTROLWINCTL.H--Clipboard Target Support
Helper Function Prototypes
void GetDataFromClipboard(void);
. . .
BOOL GetDataFromTransfer(COleDataObject * opOleDataObject);
};
. . .
Listing 7.20 MFCCONTROLWINCTL.CPP--GetDataFromClipboard
Implementation
{
// get a data object
COleDataObject oOleDataObject;
// attach it to the clipboard
if(!oOleDataObject.AttachClipboard())
return;
// transfer the data to the control
this->GetDataFromTransfer(&oOleDataObject);
}
Listing 7.21 MFCCONTROLWINCTL.CPP--GetDataFromTransfer
Implementation
{
BOOL bReturn = FALSE;
// prepare for an enumeration of the clipboard formats available
opOleDataObject->BeginEnumFormats();
// is there a text format available
if(opOleDataObject->IsDataAvailable(CF_TEXT))
{
FORMATETC etc;
// while there are formats to enumerate
while(opOleDataObject->GetNextFormat(&etc))
{
// is this a format that we are looking for?
if(etc.cfFormat == CF_TEXT)
{
// get the global data for this format
HGLOBAL hGlobal = opOleDataObject->GetGlobalData
(etc.cfFormat, &etc);
// lock down the memory
LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock
(hGlobal);
// store the data
m_cstrCaption = lpTempBuffer;
// call the global routine
this->FireChange();
// unlock the memory
::GlobalUnlock(hGlobal);
// return success
bReturn = TRUE;
}
}
}
// if we found a format
if(bReturn == TRUE)
// force the control to repaint itself
this->InvalidateControl(NULL);
// return the result
return bReturn;
}
Drag and Drop Support
Using Built-In Drag and Drop Formats
Enabling a Control as a Drag and Drop Source
FIG. 7.3
Add the WM_LBUTTONDOWN message map.Listing 7.22 MFCCONTROLWINCTL.CPP--OnLButtonDown Implementation
{
COleDataSource oOleDataSource;
// call the common data preparation function
this->PrepareDataForTransfer(&oOleDataSource);
// start the Drag and Drop operation
oOleDataSource.DoDragDrop(DROPEFFECT_COPY);
// call the base class implementation
COleControl::OnLButtonDown(nFlags, point);
}
Enabling a Control as a Drop Target Listing 7.23 MFCCONTROLWINCTL.H--CMyOleDropTarget Class
Declaration Added to the CMFCControlWinCtl Class
{
public:
CMFCControlWinCtrl * opMFCControlWinCtrl;
DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD
dwKeyState, CPoint point);
BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT
dropEffect, CPoint point);
} oMyOleDropTarget;
friend CMyOleDropTarget;
FIG. 7.4
Add the WM_CREATE message map.Listing 7.24 MFCCONTROLWINCTL.CPP--OnCreate Implementation
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;
// let's register ourselves as drop targets
oMyOleDropTarget.Register(this);
// store a reference to the control so we can communicate back to the
control
oMyOleDropTarget.opMFCControlWinCtrl = this;
return 0;
}
Listing 7.25 MFCCONTROLWINCTL.CPP--OnDragOver Implementation
COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
// if the left mouse button is being held down
if(dwKeyState | MK_LBUTTON)
{
// is there a text format available
if(pDataObject->IsDataAvailable(CF_TEXT))
return DROPEFFECT_COPY;
// everything else we can't deal with
else
return DROPEFFECT_NONE;
}
else
// not the left mouse
return DROPEFFECT_NONE;
}
Listing 7.26 MFCCONTROLWINCTL.CPP--OnDrop Implementation
{
// transfer the data to the control
return opMFCControlWinCtrl->GetDataFromTransfer(pDataObject);
}
Custom Clipboard and Drag and Drop Formats
Listing 7.27 MFCCONTROLWINCTL.H--m_uiCustomFormat Member
Variable Added to the CMFCControlWinCtrl Class
// custom format storage variable
UINT m_uiCustomFormat;
};
Listing 7.28 MFCCONTROLWINCTL.CPP--Custom Clipboard Format
Registered in the CMFCControlWinCtrl Constructor
{
InitializeIIDs(&IID_DMFCControlWin, &IID_DMFCControlWinEvents);
// TODO: Call InternalSetReadyState when the readystate changes.
m_lReadyState = READYSTATE_LOADING;
// set the alignment to the default of left
m_lAlignment = EALIGN_LEFT;
// don't forget this - DoPropExchange won't work without it
oMyDataPath.SetControl(this);
// register a custom clipboard format
m_uiCustomFormat =
::RegisterClipboardFormat(_T("MFCControlWinCustomFormat"));
}
Listing 7.29 MFCCONTROLWINCTL.CPP--PrepareDataForTransfer
Function Updated to Support Custom Clipboard Formats
opOleDataSource)
{
// get the length of the data to copy
long lLength = m_cstrCaption.GetLength() + 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_cstrCaption.GetAt(lCount);
// null terminate the string
lpTempBuffer[lCount] = `\0';
// unlock the memory
::GlobalUnlock(hGlobal);
// cache the data
opOleDataSource->CacheGlobalData(CF_TEXT, hGlobal);
// if we have custom clipboard format support
if(m_uiCustomFormat)
{
// create a global memory object
HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
sizeof(m_lAlignment));
// lock the memory down
LONG * lpTempBuffer = (LONG *) ::GlobalLock(hGlobal);
// set our data buffer
*lpTempBuffer = m_lAlignment;
// unlock the memory
::GlobalUnlock(hGlobal);
// cache the data
opOleDataSource->CacheGlobalData(m_uiCustomFormat, hGlobal);
}
}
The GetDataFromTransfer implementation is almost identical to your original
implementation (see Listing 7.30). The only difference is that the function now looks
for the custom format as well as the CF_TEXT format.
Listing 7.30 MFCCONTROLWINCTL.CPP--GetDataFromTransfer
Function Updated to Support Custom Clipboard Formats
{
BOOL bReturn = FALSE;
// prepare for an enumeration of the clipboard formats available
opOleDataObject->BeginEnumFormats();
// is there a text format available
// && there is no custom format
// || there is a custom format and the format is available
if(opOleDataObject->IsDataAvailable(CF_TEXT) && (!m_uiCustomFormat
|| (m_uiCustomFormat && opOleDataObject->IsDataAvailable
(m_uiCustomFormat))))
{
FORMATETC etc;
// while there are formats to enumerate
while(opOleDataObject->GetNextFormat(&etc))
{
// is this a format that we are looking for?
if(etc.cfFormat == CF_TEXT)
{
// get the global data for this format
HGLOBAL hGlobal = opOleDataObject->GetGlobalData
(etc.cfFormat, &etc);
// lock down the memory
LPTSTR lpTempBuffer = (LPTSTR) ::GlobalLock(hGlobal);
// store the data
m_cstrCaption = lpTempBuffer;
// call the global routine
this->FireChange();
// unlock the memory
::GlobalUnlock(hGlobal);
// return success
bReturn = TRUE;
}
// if we have custom clipboard format support
else if(m_uiCustomFormat && etc.cfFormat == m_uiCustomFormat)
{
// get the global data for this format
HGLOBAL hGlobal = opOleDataObject->GetGlobalData
(etc.cfFormat, &etc);
// lock the memory down
LONG * lpTempBuffer = (LONG *) ::GlobalLock(hGlobal);
// get the data from our data buffer
m_lAlignment = *lpTempBuffer;
// unlock the memory
::GlobalUnlock(hGlobal);
// return success
bReturn = TRUE;
}
}
}
// if we found a format
if(bReturn == TRUE)
// force the control to repaint itself
this->InvalidateControl(NULL);
// return the result
return bReturn;
}Subclassing Existing Windows Controls
Listing 7.31 MFCCONTROLSUBWINCTL.CPP--Additional Code
Requirements for ActiveX Controls that Subclass Existing Windows Controls
// CMFCControlSubWinCtrl::OnDraw - Drawing function
void CMFCControlSubWinCtrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
DoSuperclassPaint(pdc, rcBounds);
}
. . .
/////////////////////////////////////////////////////////////////////////////
// CMFCControlSubWinCtrl::PreCreateWindow - Modify parameters for CreateWindowEx
BOOL CMFCControlSubWinCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
cs.lpszClass = _T("BUTTON");
return COleControl::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CMFCControlSubWinCtrl::IsSubclassedControl - This is a subclassed control
BOOL CMFCControlSubWinCtrl::IsSubclassedControl()
{
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// CMFCControlSubWinCtrl::OnOcmCommand - Handle command messages
LRESULT CMFCControlSubWinCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
{
#ifdef _WIN32
WORD wNotifyCode = HIWORD(wParam);
#else
WORD wNotifyCode = HIWORD(lParam);
#endif
// TODO: Switch on wNotifyCode here.
return 0;
}
Subclassing an existing control is a risky proposition at best. The implementation
of the particular Windows control you are subclassing may make it difficult, if not
impossible, to subclass from within the confines of the MFC control framework. The
concept of multiple windows' handles within a single control is one area where subclassing
using MFC will come up short. It may be very difficult to reliably retrieve the secondary
window handles of the control if the original implementation does not provide access
to its child windows.
Also, some of the ActiveX features detailed in this chapter may not be appropriate
or possible because you are simply wrapping an existing window's control. Creating
a windowless control is impossible because you are depending on the window's control
for the implementation.
Dual-Interface Controls
Other ActiveX Features
Windowless Activation
Flicker-Free Activation
Unclipped Device Context
Mouse Pointer Notifications When Inactive
From Here...