YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET

 


UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"


VC++ MFC ActiveX Control Article, COM Drag - Drop  Example

 
 Data Transfer with Drag and Drop

By Fritz Onion
Published in C++ Report, April 1999 issue.

The ability to transfer information between applications on the same computer, without either application having any foreknowledge of the other, was one of the flagship features of early window-based user interfaces. This means of data transfer remains one of the most useful user-oriented features of the Windows operating system today. Drag and drop is a more recent incarnation of the classic clipboard transfer, allowing users to transfer data between applications by simply dragging between two windows (no clipboard involved). This capability is built on top of Microsoft COM technology, and has become relatively easy to add to most Visual C++ based applications.

This article will look at the infrastructure behind the drag and drop feature of Windows, and describe how you can add drag and drop to your applications using MFC helper classes. I will also introduce a template helper class that simplifies turning non-view windows into drop targets, which is particularly useful for ActiveX controls built in MFC.

There are three sample projects that accompany this article which can be downloaded from here. The first sample is a raw C++ application that displays text in a window and supports text-based drag and drop operations. The second sample implements the same functionality using MFC, and the last sample is an ActiveX control built with MFC that subclasses an edit control and supports text-based drag and drop.

The Means

Data transfer using drag and drop is performed through a standard COM interface called IDataObject. This interface is advertised by objects that know how to render data in one or more formats, and is used to clients to retrieve that data. IDataObject is used in several other places throughout OLE and ActiveX, so it is very generic, and like most of these older interfaces, it has some historical baggage associated with it. For the purposes of this article, we are only really interested in two of its methods:

interface IDataObject : IUnknown
{
	HRESULT GetData(FORMATETC *pformatetcIn,
						STGMEDIUM *pmedium);
	HRESULT QueryGetData(FORMATETC *pformatetc);
	// More methods not shown...
}

The concept behind the interface is quite straight-forward. To obtain data, a client simply calls IDataObject::GetData() on the source object, which will populate some data structure and return it to the client. What this data looks like, depends on what parameters the client passed into GetData(). In fact, before GetData() is called, a client will typically ucancode.net the object if it supports the data format that it is looking for. For example, a text processing application might understand only text data, whereas an imaging application probably understands bitmaps and metafiles. A client ucancode.nets this question by using the IDataObject::QueryGetData() method.

IDataObject* pDataObj = //obtain pointer somehow 
	// ucancode.net for text in a global buffer
FORMATETC formatEtc = 
		{CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};

if (SUCCEEDED(pDataObj->QueryGetData(&formatEtc)) )
	// data format is there - now retrieve it 

This query actually ucancode.nets two questions - do you have the data in the format I am looking for, and can you provide it in the storage medium I prefer? These questions are expressed through the FORMATETC structure, which is the only parameter to QueryGetData(). This structure contains five fields, only two of which most drag and drop operations care about - cfFormat and tymed. The cfFormat field is simply the clipboard format of the data which could be a standard format (CF_TEXT, CF_BITMAP, ...) or a custom registered application-specific format. The tymed field describes the desired storage medium (or media) for the transfer of data. It can be any combination of the following bit-flags:

	TYMED_HGLOBAL 		// Global buffer
	TYMED_FILE			// File name
	TYMED_ISTREAM		// IStream*
	TYMED_ISTORAGE		// IStorage*
	TYMED_GDI			// HBITMAP
	TYMED_MFPICT		// HMETAFILE
	TYMED_ENHMF		// HENHMETAFILE

It may seem curious to make distinctions between so many in-memory formats, but it is necessary in order to properly reclaim the resource (HeapFree versus DeleteObject for a global buffer and an HBITMAP, for example).

Once the client has found a data format he likes, he then requests the data by calling the IDataObject::GetData() method. For example, to retrieve the text data queried for above, a client would write (using the same FORMATETC structure):

STGMEDIUM stgMedium;
HRESULT hr = pDataObj->GetData(&formatEtc, &stgMedium);
if (SUCCEEDED(hr))
{
	HGLOBAL gmem = stgMedium.hGlobal;
	TCHAR* str = (TCHAR*)GlobalLock(gmem);
	// use str
	GlobalUnlock(gmem);
	::ReleaseStgMedium(&stgMedium);
}

The STGMEDIUM structure is a discriminated union for each of the media types, using the TYMED value as the discriminator. The API function ReleaseStgMedium() releases the medium properly.

This client-side code should give you an idea of what's involved with providing IDataObject for a data source. First of all, it is typically a stand-alone COM object, independent of your primary data class. Most data providers create an instance of a class that implements IDataObject, provide that class with a cache of the data necessary for client retrieval, and send it off to deliver the data on its own.

As an example, let's build a class that supports IDataObject and is capable of providing access to a string buffer. The class will simply contain a pointer to a string that will be rendered on request.

class StrDataObj : public IDataObject
{
private:
	ULONG	m_cRef;		// Reference count
	TCHAR*	m_strText;
public:
	StrDataObj() : m_cRef(0), m_strText(0) 	{}
	void SetText(TCHAR* text) { m_strText = text; }
	// IUnknown and IDataObject methods would go here
};

A good data provider will provide as many possible formats as it can, to let the largest number of clients possible receive its data. The two interesting methods of IDataObject for a data provider are QueryGetData() and GetData(). QueryGetData() should respond yes or no indicating whether the format requested is available. As a very simple data provider implementation, assume that we only provide text in a global buffer. Our QueryGetData() implementation would look like:

STDMETHODIMP 
StrDataObj::QueryGetData(FORMATETC* pformatetc)
{
	if ((pformatetc->dwAspect & DVASPECT_CONTENT) &&
		(pformatetc->tymed & TYMED_HGLOBAL) &&
		(pformatetc->cfFormat == CF_TEXT))
		return S_OK;
	return DV_E_FORMATETC;	
}

And, the actual data retrieval function might look like:

STDMETHODIMP StrDataObj::GetData(FORMATETC *pformatetcIn, 
						STGMEDIUM *pmedium)
{
	if ((pformatetcIn->dwAspect & DVASPECT_CONTENT) &&
		(pformatetcIn->tymed & TYMED_HGLOBAL) &&
		(pformatetcIn->cfFormat == CF_TEXT))
	{
		void* pMem = HeapAlloc(GetProcessHeap(), 
				HEAP_ZERO_MEMORY, 
				sizeof(TCHAR)*(lstrlen(m_strText)+1));
		lstrcpy((TCHAR*)pMem, m_strText);

		pmedium->tymed			= TYMED_HGLOBAL;
		pmedium->hGlobal		= pMem;
		pmedium->pUnkForRelease = NULL;
		return S_OK;
	}
	return DV_E_FORMATETC;
}

It is important to note that unlike the name implies, IDataObject is not a generic data transfer interface. It was designed to transfer text or image data between applications, and is not well suited for transferring large blocks of data efficiently between applications (especially across networks). Just as you wouldn't use the clipboard to perform high-speed data transfer between applications, don't use IDataObject either as it has many of the same constraints as the clipboard, and is not designed for efficiency out of process. For a better solution to generic data transfer in COM, take a look at conformant arrays.

The mechanism

We now have the means for transferring the data, and are ready to look at the mechanism of the drag and drop process. Initiating a drag and drop operation is actually a bit simpler than responding to one, so we will look at the source side first. Given the IDataObject implementation provided by our StrDataObj class above, we want to be able to pass a pointer to that object to some client, based on user mouse input. The user should be able to click the mouse in our source application's window, drag to another window (probably in another application), and release, causing some data to transfer (or copy if the control key is held down).

Most of the mechanics of the drag and drop process are implemented for you by the operating system. As a data source, you simply provide two pointers - one to your data object, and one to a drop source object which allows you to provide feedback during the drag. This source object must implement the IDropSource interface, and should have a standard heap-based COM object lifetime. The interface itself is quite simple, providing two methods which will be called during the drag process:

interface IDropSource : IUnknown
{
	HRESULT QueryContinueDrag (BOOL fEscapePressed,
								DWORD grfKeyState);
	HRESULT GiveFeedback (DWORD dwEffect);
}

QueryContinueDrag() is called whenever the keyboard is touched, or a mouse key is pressed during a drag, giving the source a chance to abort the drag process. Typically a source will abort the drag if the escape key was pressed, and perform a drop if the left mouse button is released. The other method, GiveFeedback() provides a means of changing the cursor based on what the effect of the drag operation will be. For example, if the drag is a copy, the cursor usually indicates so by including a '+' sign as part of the icon. Most of the time sources will want to use the default cursors which is easily done by returning a special return code DRAGDROP_S_USEDEFAULTCURSORS from this method. So a typical drop source implementation will look like:

class DropSource : public IDropSource
{
private:
	ULONG m_cRef;	// Reference count
public:
	DropSource() : m_cRef(0) {}
		// IUnknown methods not shown
	STDMETHODIMP QueryContinueDrag(BOOL fEscapePressed, 
										DWORD grfKeyState)
	{
		if (fEscapePressed)
			return DRAGDROP_S_CANCEL;
		if (!(grfKeyState & MK_LBUTTON))
			return DRAGDROP_S_DROP;
		return S_OK;
	}
    STDMETHODIMP GiveFeedback(DWORD dwEffect)
	{
	return DRAGDROP_S_USEDEFAULTCURSORS;
	}
};

Now, to actually initiate a drag and drop transfer, we need to hand off our two pointers to the DoDragDrop() function, typically in response to an WM_LBUTTONDOWN message. Thus our handler might look like:

void OnLButtonDown(...)
{
	DropSource* pDropSource = new DropSource;
	pDropSource->AddRef();
	DataObject* pDataObj	= new DataObject;
	pDataObj->AddRef();
	pDataObj->SetText(/*some string in our app*/);

	DWORD de;
	HRESULT hr = DoDragDrop(pDataObj, pDropSource, 
				DROPEFFECT_COPY|DROPEFFECT_MOVE, &de);
	if ((hr == DRAGDROP_S_DROP) &&
		(de == DROPEFFECT_MOVE) )
		// remove our string
	pDataObj->Release();
	pDropSource->Release();
}

The DoDragDrop() method takes the IDataObject and IDropSource pointers, plus what type of drop we want to support, and an output parameter describing what type of drop occurred. When invoked, DoDragDrop() takes over our message loop in a modal fashion, returning only once the drop has occurred or been aborted. When it takes over, it tracks the mouse, making calls back into our IDropSource interface, and checking the window under the current mouse position to see if it is in fact a drop target. Once complete, our source application should check to see if the drop was a move or copy, and affect our local data appropriately.

The other side of the picture is the drop target implementation. To act as a drop target, a client must provide a window that has been registered with a drop target implementation (an object supporting IDropTarget). The IDropTarget interface is called during a drag drop operation to notify a potential drop target that a drop may be performed, and to ucancode.net a drop target if it can in fact accept the drop. A target application needs to build a COM class that implements IDropTarget, and then register an instance of that class with any window that wants to be a drop target. To match our source class, let's build a target class that accepts drops if they are text in global buffers.

class DropTarget : public IDropTarget
{
private:
	ULONG			m_cRef;		// Reference count
	bool			m_bCanAcceptDrop;
public:
	DropTarget() : m_cRef(0), m_bCanAcceptDrop(false) {}

	// IUnknown methods not shown
	STDMETHODIMP DragEnter(IDataObject *pDataObj, 
		DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
	STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt,
							DWORD *pdwEffect);
	STDMETHODIMP DragLeave();
	STDMETHODIMP Drop(IDataObject *pDataObj, 
		DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
};

Notice that in addition to the reference count, we added a Boolean data member to our class to indicate whether we can accept a drop or not. The first method of concern is DragEnter(). This method is called during a drag operation whenever the mouse cursor first enters our window (the one we've associated with this target object). Our implementation must check the incoming data object to see if it contains data in a format we understand, and then indicate whether a drop will occur or not by populating the pdwEffect out parameter with the appropriate drop effect (checking for the control key to see if it should be a copy or move). It is also in this function that we will populate our data member indicating whether or not we can handle the current drop, which we will use later in our DragOver() implementation.

STDMETHODIMP DropTarget::DragEnter(IDataObject* pDataObj, 
		DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
{
	FORMATETC fmtetc = {CF_TEXT, 0, DVASPECT_CONTENT, 
						-1, TYMED_HGLOBAL};
	if (FAILED(pDataObj->QueryGetData(&fmtetc)))
	{
		*pdwEffect = DROPEFFECT_NONE;
		m_bCanAcceptDrop = false;
	}
	else
	{
		m_bCanAcceptDrop = true;
		*pdwEffect = (grfKeyState & MK_CONTROL) ?
							DROPEFFECT_COPY : DROPEFFECT_MOVE;
	return S_OK;
}

Our next method, DragOver(), will be called every time the mouse moves over our window. This allows fine-grained control over dropping at different locations in a window if desired. Our implementation will simply populate the pdwEffect field indicating whether or not the drop will occur, and whether the drop will be a copy or a move based on the control key.

STDMETHODIMP DropTarget::DragOver(DWORD grfKeyState, 
					POINTL pt, DWORD *pdwEffect)
{
	if (!m_bCanAcceptDrop)
		*pdwEffect = DROPEFFECT_NONE;
	else if (grfKeyState & MK_CONTROL)
		*pdwEffect = DROPEFFECT_COPY;
	else
		*pdwEffect = DROPEFFECT_MOVE;
	return S_OK;
}

The DragLeave() method is called when the mouse moves out of our window. It simply provides us with a way of cleaning up any resources we might have allocated in DragEnter. Since we didn't allocate anything, our implementation won't really do anything - although it will need to reset our Boolean flag indicating that we will accept the current drop. The last method, Drop() is called when the user releases the mouse over our window, and is where the actual data transfer occurs. Notice that it takes an IDataObject pointer, which we will use to extract our text data. We will also indicate again whether the drop was a copy or a move.

STDMETHODIMP DropTarget::DragLeave()
{
	m_bCanAcceptDrop = false;
		return S_OK;
}
STDMETHODIMP Drop(IDataObject *pDataObj, 
		DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	FORMATETC fmtetc = {CF_TEXT, 0, DVASPECT_CONTENT, 
							-1, TYMED_HGLOBAL};
	STGMEDIUM stgMedium;
	HRESULT hr = pDataObj->GetData(&fmtetc, &stgMedium);
	if (FAILED(hr)) return hr;
	TCHAR* pStr = (TCHAR*)GlobalLock(stgMedium.hGlobal);
	// save pStr text in our app somehow
	GlobalUnlock(gmem);
	::ReleaseStgMedium(&stgMedium);

	*pdwEffect = (grfKeyState & MK_CONTROL) ?
						DROPEFFECT_COPY : DROPEFFECT_MOVE;
	return S_OK;
}

The last detail to be taken care of in the target side is to associate an instance of our DropTarget class with a window, turning it into a drop target. This is done with the RegisterDragDrop method, which takes an HWND and an IDropTarget pointer, and actually caches the IDropTarget pointer in the extended bits of the window handle. This allows the dragging operation to easily check and see if any given window handle is in fact a drop target, and if it is, to extract the IDropTarget interface pointer and make the appropriate calls. This association is often made when the window itself is created, usually in a handler for the WM_CREATE message.

void OnCreate()
{
	DropTarget* pDropTarget = new DropTarget;
	pDropTarget->AddRef();
	HWND hWnd = // grab our window handle somehow
	RegisterDragDrop(hWnd, pDropTarget);
	pDropTarget->Release();
}

One other detail that should be mentioned is that since both the data source and drop target applications will be using COM, they must initialize the COM libraries. This is typically done by calling CoInitialize() at the beginning of the process, but if drag and drop features are being used, you must call OleInitialize() instead, which will set up the additional runtime infrastructure needed to execute drag and drop operations. The corresponding cleanup method is OleUninitialize().

int WinMain(...)
{
	if (FAILED(OleInitialize(0)))
		return -1;

	// Main code and message loop would go here

	OleUninitialize();
}

Help From MFC

Although building support for drag and drop from scratch is not too difficult, MFC does simplify things by providing a couple of wrapper classes that make some common assumptions about the way you will provide drag and drop support in your application. One of the most useful classes is its COleDataSource class. This class provides a full implementation of the IDataObject interface, and allows you to populate its internal list of data structures with as many data types as you want to support in your drop operation with its cache methods.

class COleDataSource : public CCmdTarget
{
public:
	void CacheGlobalData(CLIPFORMAT cfFormat, 
		HGLOBAL hGlobal,	LPFORMATETC lpFormatEtc = NULL);	void CacheData(CLIPFORMAT cfFormat, 
		LPSTGMEDIUM lpStgMedium,	LPFORMATETC lpFormatEtc);
	DROPEFFECT DoDragDrop(...);
	// More functions not shown
};

Notice that it also provides a wrapper around the DoDragDrop() method, which implicitly passes this instance of the data source and a default implementation of IDropSource (an instance of MFC's COleDropTarget class) to the API call. Thus the process of sourcing a drag and drop operation using MFC can be as simple as:

void CMyWnd::OnLButtonDown(...)
{
	COleDataSource ds;
	// Assume m_String is a CString data member of this
	// class containing the string data to transfer
	void* pMem = HeapAlloc(GetProcessHeap(), 
			0, sizeof(TCHAR)*(m_String.GetLength()+1));
	lstrcpy((TCHAR*)pMem, m_String);

	ds.CacheGlobalData(CF_TEXT, pMem);
	ds.DoDragDrop(DROPEFFECT_COPY|DROPEFFECT_MOVE);
}

The COleDataSource class' implementation of DoDragDrop() will actually not initiate the drop operation until the user moves out of a bounding rectangle, which means that trivial mouse clicks will not start a drag operation - the user must really drag before the operation begins. This class also provides a way of providing the data through a callback instead of caching it. This is a good idea if you have a large amount of data, or your have a large number of data formats, which really shouldn't be cached unless the user actually drops the data somewhere. This is achieved by deriving a new class from COleDataSource, overriding OnRenderData() to receive the notification, and use the DelayRenderData() method to indicate data is available instead of the cache methods.

If you're trying to turn a CWnd-derived class into a drop target, MFC provides the COleDropTarget class to help. In fact, this class assumes that you will usually add drop target support to your view class (CView-derived), so its implementation is to simply forward each of the four notification methods of IDropTarget onto the view class it is associated with (if it is in fact associated with a view). For example, the DragEnter() method implementation looks like:

DROPEFFECT COleDropTarget::OnDragEnter(CWnd* pWnd, 
			COleDataObject* pDataObject,
			DWORD dwKeyState, CPoint point)
{
	if (!pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		return DROPEFFECT_NONE;

	CView* pView = (CView*)pWnd;
	return pView->OnDragEnter(pDataObject, 
							dwKeyState, point);
}

Notice that if the incoming window pointer is not a view, it will return no drop effect, otherwise it forwards the request on to the method of the same name in the view class. This makes turning your view class into a drop target quite straight forward - add a data member to your view class of type COleDropTarget, override the four notification methods in your view class, and call the register method of the COleDropTarget class when your view is first created, as shown below:

 class CTextEditView : public CView
{
private:
	bool				m_bCanAcceptDrop;
	COleDropTarget		m_DropTarget;
public:
	virtual DROPEFFECT OnDragEnter();
	virtual DROPEFFECT OnDragOver();
	virtual void OnDragLeave();
	virtual BOOL OnDrop();

	int OnCreate(LPCREATESTRUCT lpCreateStruct)
	{ 	
		CView::OnCreate(lpCreateStruct); 
		m_DropTarget.Register(this);	
		return 0;
	}
	//...
};

The implementation of the four notification functions would essentially be identical to our earlier IDropTarget implementation, so I won't bother to show them again. For the full implementation, please refer to the online samples mentioned earlier.

A More Generic Drop Target

Although being able to turn your view class into a drop target is often what you want to do, there are occasions when you may want to turn non-view windows into drop targets as well. For example, adding drag and drop support to an ActiveX control is often desirable. Given the implementation of COleDropTarget, the only obvious way to provide drop target support to a non-view window is to derive a new class from COleDropTarget, and somehow forward the four notifications to your window by hand. A slightly more generic approach, however, is to define a template-based forwarding class derived from COleDropTarget that could be used in any CWnd-derived class. This class will assume that the CWnd-derived class it is being registered with will provide the four notification methods of IDropTarget as non-virtual methods with the same signature as the methods in COleDropTarget, and simply forward the notifications to the class.

 template 

class CForwardingTarget : public COleDropTarget
{
private:
		T* m_pForward;
public:
		CForwardingTarget() : m_pForward(NULL){} 

		BOOL Register(T* pForward)
		{ 
			m_pForward = pForward;  
			return COleDropTarget::Register(pForward); 
		}
		virtual DROPEFFECT OnDragEnter(...)
		{ return m_pForward->OnDragEnter(...); 	}
		virtual DROPEFFECT OnDragOver(...)
		{ return m_pForward->OnDragOver(...);		}
		virtual BOOL OnDrop(...)
		{ return m_pForward->OnDrop(...); 			}
		virtual void OnDragLeave(...)
		{ m_pForward->OnDragLeave(pWnd); 			}
};

As an example use of this new class, consider an ActiveX control built in MFC to which we would like to add drop target support. We would simply add a data member of our new class passing our class type in to instantiate the template, and provide implementations of the four notification methods. Then in our WM_CREATE handler, we would register the drop target:

class CMyControl : public COleControl
{
private:
CForwardingTarget<CMyControl> m_DropTarget;
int OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	COleControl::OnCreate(lpCreateStruct);	
		m_DropTarget.Register(this);	
		return 0;
}
	DROPEFFECT OnDragEnter(?;
	DROPEFFECT OnDragOver(?;
	BOOL OnDrop(?;
	void OnDragLeave(CWnd* pWnd);
};

Again, for the complete implementation of an ActiveX control with drop target support in MFC, please refer to the online samples.

Summary

Drag and drop is a useful way of transferring data between applications in Windows, and should be added to any application that works with data that might be recognizable by any other application (most commonly text and images). The flexible interfaces that define this operation allow for a variety of implementations, including drag and drop within a single application, or even within a single window. MFC provides wrapper classes for drag and drop operations, and makes turning your view class into a drop target quite straight-forward, and with the generic forwarding class presented, it is equally easy for any CWnd-derived class. I hope this article encourages you to start dragging and dropping today!

 

 

Copyright ?1998-2024 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@ucancode.net