Click here to Skip to main content
Click here to Skip to main content

Implementing Reusable Drag & Drop Classes

By , 4 Jun 2001
 

Introduction

I tried to create as generic a Drag and Drop classes as possible. Here is what I came up with. I think it's a good starting point if you're looking to add drag and drop support to your app.

The demo project has sample code for various clipboard formats:

  • CF_TEXT
  • CF_HDROP
  • CF_BITMAP
  • CF_DIB
  • CF_ENHMETAFILE

and MEDIUMs

  • TYMED_HGLOBAL
  • TYMED_ISTREAM
  • TYMED_ENHMF
  • TYMED_GDI.

Some screenshots of image drag and drop from static window:


(Fig. 1. Dragging from static window to WordPad)

(Fig. 2. Using Clipboard)

(Fig. 3. Pasting the above clipboard contents into WordPad)

Usage:

To enable your window as a DropTarget:

  • Derive a class from CIDropTarget.
  • Override OnDrop. Return true or false from this method. If true, base class will free the medium. If false, it won't free the medium.
  • Call ::RegisterDragDrop for your window.
  • Add Supported formats by calling CIDropTarget::AddSuportedFormat.
  • Optionally override other methods such as DragOver and DragLeave.
    I used it for the tree to highlight the current item.

Example:

class CTreeDropTarget : public CIDropTarget
{
public:
      virtual bool OnDrop(FORMATETC* pFmtEtc, STGMEDIUM& medium, DWORD *pdwEffect)
      {
            if(pFmtEtc->cfFormat == CF_TEXT && medium.tymed == TYMED_HGLOBAL)
            {
              // Handle it
            }
          return true;
      }
     // etc...
};

In your Window derived class create a member of CTreeDropTarget. Then initialize it like this:

{
// ...
m_pDropTarget = new CTreeDropTarget(m_hWnd);
RegisterDragDrop(m_hWnd,m_pDropTarget);
// create the supported formats:
FORMATETC ftetc={0}; 
ftetc.cfFormat = CF_TEXT; 
ftetc.dwAspect = DVASPECT_CONTENT; 
ftetc.lindex = -1; 
ftetc.tymed = TYMED_HGLOBAL; 
m_pDropTarget->AddSuportedFormat(ftetc); 
ftetc.cfFormat=CF_HDROP; 
m_pDropTarget->AddSuportedFormat(ftetc);
// ...
}

That's all for drop target.

To enable your window as the Drag and Drop source:

  • Catch the Windows message that initiates the drag and drop such as TVN_BEGINDRAG.
  • In the message function handler create new CIDataObject and CIDropSource.
  • Create the clipboard formats and medium for those formats.
  • Call SetData to add the clipboard formats and medium to DataObject. Second parameter to SetData indicates if DataObject should take the ownership of medium or not. If set to TRUE, then DataObject takes the ownership of your medium, you don't need to free it. Otherwise it will make a copy of your medium without releasing the one you gave it.

Eaxmple:

 LRESULT OnBegindrag(...)
{
   CIDropSource* pdsrc = new CIDropSource;
   CIDataObject* pdobj = new CIDataObject(pdsrc);
   // Init the supported format
   FORMATETC fmtetc = {0}; 
   fmtetc.cfFormat = CF_TEXT; 
   fmtetc.dwAspect = DVASPECT_CONTENT; 
   fmtetc.lindex = -1; 
   fmtetc.tymed = TYMED_HGLOBAL;
  // Init the medium used
  STGMEDIUM medium = {0};
  medium.tymed = TYMED_HGLOBAL;
  // medium.hGlobal = init to something
  // Add it to DataObject
  pdobj->SetData(&fmtetc,&medium,TRUE); // Release the medium for me
  // add more formats and medium if needed
  // Initiate the Drag & Drop
  ::DoDragDrop(pdobj, pdsrc, DROPEFFECT_COPY, &dwEffect);
} 

To use the shell's drag image manager (comes with Windows 2000):

You don't need to add the support for it if you are acting as drop target. It is encapsulated in CIDropTarget class.
If you're acting as data source:

  • Create an instance of CDragSourceHelper before calling ::DoDragDrop.
  • Call CDragSourceHelper::InitializeFromWindow or CDragSourceHelper::InitializeFromBitmap.

Adding the Copy/Paste through clipboard is not much work either.

Example:

LRESULT OnContextMenu(...)
{
  // ...
   CIDataObject* pdobj = new CIDataObject(NULL);  
   // Init FORMATETC and STGMEDIUM just like before
   // Add the format and medium to Dataobject
  pdobj->SetData(&fmtetc,&medium,TRUE);
  // Add data to clipboard
  OleSetClipboard(pdobj);
  OleFlushClipboard(); //render the data on clipboard, so it's available even if we close the app 
 // ...
}

References:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Leon Finker
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralDrag images don't show on Win2000member^Mo^21 May '04 - 10:00 
Hi, sorry to bother you again. But I've been working mostly on XP here with my program, but at this moment I'm testing it on Windows 2000. And the drag image does not appear. It doesn't do that in your demo project either.
 
Do you know anything about this problem? According to the documentation in msdn it should be supported. What've you been working with yourself? If you're also using 2000, then perhaps it's a problem with the computer over here.
 
Thanks.
GeneralRe: Drag images don't show on Win2000memberLeon Finker21 May '04 - 13:26 
Hi,
 
Interesting, there was one of the previous posts saying "No drag image on XP" Smile | :)
 
I've been using Windows 2000 and the image was there. But I've upgraded to Windows XP since then. I'll try next week when I have access to Win2k and let you know.
 
Thanks a lot

GeneralRe: Drag images don't show on Win2000member^Mo^21 May '04 - 15:29 
Yea, I saw that one too hehe.
 
Thanks for taking your time on testing it, I'd really appreciate it. I'll see if I can get someone to test it on another win2k machine in the meantime.
GeneralRe: Drag images don't show on Win2000memberLeon Finker24 May '04 - 8:44 
I tried it on win2k (sp4+latest updates). Both IDragSourceHelper::InitializeFromBitmap and IDragSourceHelper::InitializeFromWindow return E_FAIL. I remembered, it
did work on win2k that i used for development. You can see it from screenshot.
Maybe one of updates breaks it? Or maybe it doesn't like the bitmap given to it.

GeneralRe: Drag images don't show on Win2000member^Mo^24 May '04 - 11:43 
I don't know. I'm testing with SP3 here now, and it doesn't work.
 
I put in some debug lines, and GetLastError returns error #8: "Not enough storage is available to process this command.". Although I'm not so sure if this makes any sense when it comes to OLE.
 
The HRESULT returned ended up being an "Unspecified error". So that isn't very helpful either
Generalit works on xp pro and ONE questionmemberbaboguru28 Jun '04 - 16:30 
thanks for the great aticle first
 
i found that
once drag image got out of the DragDrop window, its image is
changed to DEFAULTConfused | :confused: drag image
 
draging image is supposed to preserve itself out of the drag source dialog( DragDrop dialog in demo)??Confused | :confused: Confused | :confused:
is that right behavior?
 
actually i would like to preserve original drag image anyplace out of the the original drag source window?
any solution??Sigh | :sigh:
GeneralRe: it works on xp pro and ONE questionmemberLeon Finker29 Jun '04 - 14:24 
Yes that's the correct behavior. The mouse cursor state is controlled by whatever drop target window the mouse is over and by the responses of that drop target. Otherwise how would user know if they can drop on underlying window or not? You can most likely change the default behavior. To provide your own cursor return S_OK from GiveFeedback. And draw image manually if you want it.
GeneralRe: it works on xp pro and ONE questionmemberbaboguru30 Jun '04 - 15:14 
thanks for the advice
one more beginner question..Blush | :O
where shoud i put the "draw image" code in your code(reusable dnd code).
some hints would be appreciated. i hope its not too much bother though Smile | :)
anyway i think i have to google for some sample codes, "creating custom
drag image"
thanks alot again
GeneralRe: it works on xp pro and ONE questionmemberLeon Finker30 Jun '04 - 15:43 
I never tried it myself. Try putting the code in GiveFeedback and returning S_OK
GeneralAccess violation with MFCmember^Mo^13 Apr '04 - 7:55 
Hi, first of all: Great class!
 
Second: I had access violations when using it in an MFC project. The problem is the CEnumFormatEtc class. You've created on of your own, but apparently MFC also implements a class with the same name. So every time AddRef was called in CIDataObject::EnumFormatEtc() it somehow ended up in the destructor of MFC's version of CEnumFormatEtc (don't ask me how). Anyway, this kept raising an access violation all the time.
 
As a solution, I put all your classes in a seperate namespace, and that seemed to solve the problem.
GeneralRe: Access violation with MFCmemberLeon Finker13 Apr '04 - 15:28 
Hi,
 
It's probably a good idea to bypass custom implementation of IEnumFORMATETC and use CreateFormatEnumerator instead:
http://msdn.microsoft.com/workshop/networking/moniker/reference/functions/createformatenumerator.asp
 
I only saw this function after the fact, otherwise I would have probably used it instead of custom implementation Smile | :)
 
Thanks a lot for the comments
GeneralAnother thingmember^Mo^21 Apr '04 - 10:24 
Another thing. In the function CIDropTarget::QueryDrop() there is the part where you try to figure out what kind of drop effects are possible:
[code]
if(*pdwEffect == 0)
{
// ...
}
[/code]
The first thing you test for is whether there's a copy operation possible. Here a problem arises, if a drop target would accept both copy and move operations, the default action is copy instead of move. As far as I know, move would be the default operation if no modifier keys are being pressed (CTRL in particular).
The solution is simple, just switch the tests like this:
[code]
if (DROPEFFECT_MOVE & dwOKEffects)
*pdwEffect = DROPEFFECT_MOVE;
else if (DROPEFFECT_COPY & dwOKEffects)
*pdwEffect = DROPEFFECT_COPY;
[/code]
This way the default operation will become move, and copy will be the default when CTRL is held.
 

PS. How the heck do I get code sections without the <pre> tags? UBB doesn't seem to be working :/
Well, maybe UBB just doesn't work for me Confused | :confused:
GeneralRe: Another thingmemberLeon Finker21 Apr '04 - 14:27 
oops Smile | :)
 
Thanks for spotting this
 
I think <pre>   tag is the way
Generalmemory leaks!memberFPF5 Sep '03 - 2:16 
Eacht DragDrop Action (to an other application) leaves the CIDropSource.
The ReferenceCount of the CIDropSource is zero but the debugger dumps it!?
 
Detected memory leaks!
Dumping objects ->
{452} normal block at 0x00B47C28, 12 bytes long.
Data: < $b > 88 24 62 00 00 00 00 00 01 CD CD CD
Object dump complete.
 

Does anybody know the reason for this?
 

 
FPF
GeneralRe: memory leaks!memberLeon Finker5 Sep '03 - 5:53 
Hello,
 
Are you using demo project from the article or your own code?
 
I would suggest using new atldbgmem.h in atl7+
 
To use it in demo project, just include these two lines
before any other includes in stdafx.h:
 
#include <windows.h>
#include <atldbgmem.h>
 

Put this line at the end of your WinMain, in
demo project DragDrop.cpp:
 
AtlDumpMemoryLeaks();
 

Run in debug mode (F5). Try same drag & drop as before.
Exit the app normally. Then in the output window you
should get the filename and line number that is causing the leak.
 
For example i tried the demo project from this article, it only showed
leaks from atlbase.h CDynamicStdCallThunk::Init. And it's only because global
CAppModule _Module; is not destroyed yet at the point when dump memory
leaks is called. If you do this for testing purposes in DragDrop.cpp:
 
CAppModule _Module;
struct C
{
      ~C(){AtlDumpMemoryLeaks();}
};
 
Then no leaks show up.
 

I hope that helps

GeneralRe: memory leaks!memberFPF7 Sep '03 - 19:57 
Hi,
 
i'm using the code in an MFC application
where AtlDumpMemoryLeaks() could not be used.
AfxDumpMemoryLeaks() does the same job here.
 
But including additional traces i found the problem:
 
CIDropSource pdsrc is never AddRef and Released and thus not deleted.
Adding the following two lines fixes the problem.
 
LRESULT OnBegindrag(...)
{
CIDropSource* pdsrc = new CIDropSource;
CIDataObject* pdobj = new CIDataObject(pdsrc);
 
pdsrc->AddRef(); // added FPF
 
// Init the supported format
FORMATETC fmtetc = {0};
fmtetc.cfFormat = CF_TEXT;
fmtetc.dwAspect = DVASPECT_CONTENT;
fmtetc.lindex = -1;
fmtetc.tymed = TYMED_HGLOBAL;
// Init the medium used
STGMEDIUM medium = {0};
medium.tymed = TYMED_HGLOBAL;
// medium.hGlobal = init to something
// Add it to DataObject
pdobj->SetData(&fmtetc,&medium,TRUE); // Release the medium for me
// add more formats and medium if needed
// Initiate the Drag & Drop
::DoDragDrop(pdobj, pdsrc, DROPEFFECT_COPY, &dwEffect);
 
pdsrc->Release(); // added FPF
 
}
 
Thanks.
 
...and it works!
 
FPF
GeneralRe: memory leaks!memberLeon Finker8 Sep '03 - 3:02 

Yep, the demo project does that.
 

I hope you find these classes useful! Smile | :)
QuestionHow do I store raw data from my application to a disk file through drag and drop ...sussAnonymous10 May '03 - 14:00 
I want to drag a picture or text from my application and drop it onto an explorer window and I want it to be saved as a file. How do I do that Confused | :confused:
GeneralCompiler ErrormemberWhite Lie20 Feb '03 - 16:24 
When I compiled this code, there are some errors happened.
The compiler environment is VC6, VS SP5, Win2K, WTL 7.0.
The errors are described as the following:
%VDDIR%\microsoft visual studio\vc98\wtl\include\atlframe.h(273) : error C2501: 'LPNMREBARCHEVRON' : 'identifier' : missing storage-class or type specifiers.
Confused | :confused:
 
White Lie.
GeneralRe: Compiler ErrormemberJim Crafton20 Feb '03 - 17:02 
Does WTL 7 need the latest Platform SDK ? That's what it sounds like
 
¡El diablo está en mis pantalones! ¡Mire, mire!
 
Real Mentats use only 100% pure, unfooled around with Sapho Juice(tm)!
GeneralWon't compile...memberNutritiousTreat11 Jan '03 - 13:22 
I can't compile this code...
 
My compiler (VC6 SP4, Win2k) can't
find "IDragSourceHelper", "CLSID_DragDropHelper", and "IID_IDragSourceHelper".. What's more, I can't find them in any header by grepping.
 
I am writing a vanilla win32 app, no MFC... no ATL includes other than what the source code includes.
 
...help? Sniff | :^)
 
Brian
GeneralRe: Won't compile...memberLeon Finker12 Jan '03 - 7:27 
Hello Brian,
 
This is defined in ShlObj.h
of Platform SDK.
http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
 
I only used CSimpleArray from atlbase.h
GeneralInvalid Drag & Drop Image in ListViewmemberPark Dong-Hyun30 Oct '02 - 20:43 
My English is poor. Sorry.Smile | :)
 
First, You are a very good programmer.
I need your advices.
I modify your source and test.
 
maildlg.h
----------------------------------------------------------------------
....
if(!m_listview.InitDragDrop())
return -1;
 
//{{ this is my code begin.
SHFILEINFO sfi;
HIMAGELIST himl;
ZeroMemory(&sfi, sizeof(SHFILEINFO) );
himl = (HIMAGELIST)SHGetFileInfo(_T("c:\\"), 0, &sfi, sizeof(SHFILEINFO),
SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
m_listview.SetImageList(himl, LVSIL_SMALL);
//}} this is my code end.

 
m_listview.InsertColumn(0, "Column 1", LVCFMT_LEFT , 100, -1);
m_listview.InsertItem(0,"item1");
m_listview.InsertItem(1,"item2");
m_listview.InsertItem(2,"item3");
....
----------------------------------------------------------------------
 
When i drag list item with icon, Drag images are not full-colored.
InitializeFromWindow() don't return valid image.
What's problem? Blush | :O

GeneralNo drag image on XPsussandre moreira14 Sep '02 - 9:17 
Hi,
 
Very usefull class, thank you very much.
But when running on XP, I cannot see any image being dragged, although the drag and drop works.
 

QuestionHow to make DROP of big files non-blocking ?memberBenjamin.Mayrargue14 May '02 - 5:36 
My program is a DROP SOURCE.
I can drag a file from my app and drop it into Explorer
and the file is copied from it's original location to the explorer folder.
It works nice BUT is blocking the main thread of my app.
You can see this happen when you drag/drop big files: the DROP SOURCE is freezed and the UI becomes ugly (not redrawn...).
 
I've tried these things:
* Do drag/drop into another thread:
impossible: you can drag/drop only from the main thread (is this right ?)
* Try delayed rendering.
doesn't work: still in the main thread.
 
I know there is a solution because whe you drag a bg file frmom explorer
into another explorer it doesn't block either the source or target window.
 
Any idea ?
How to make DROP of big files non-blocking ??




AnswerRe: How to make DROP of big files non-blocking ?memberLeon Finker14 May '02 - 6:46 
Hi,
 
If your target is only winme+ and win2k+ then
you can look at IAsyncOperation. As far as i know
that's what current explorer uses. In older versions
i remeber it was freezing the explorer window.
 
Another easy thing to do is when you're asked for the Data,
create the thread for actual file copying and wait on the
thread handle to exit with let's say 1 sec intervals.
Meanwhile dispatch messages in between the waits. Just make sure
you don't start another drag/drop while you're already in one.
But you can queue it for example.
 
Thanx
GeneralRe: How to make DROP of big files non-blocking ?memberBenjamin.Mayrargue14 May '02 - 10:57 
Excellent !
That's what I was looking for.
 
It seems the default MFC implementation of a DropSource object does not have this interface. So I need to derive a new class to do it.
 
Maybe there is a way to do it using delayed rendering,
but nothing I tried worked.
 
Another question:
the documentation stated that any software with drag/drop enabled must be in COM's 'Single threaded model'.
Now say I need a 'Free Threaded model'. Am I stuck in this old COM model ?
 
Thks again for IAsyncOperation,
b.

GeneralRe: How to make DROP of big files non-blocking ?memberLeon Finker14 May '02 - 11:27 
Yep you're stuck with STA because
OLE Drag and Drop is initialized
by calling OleInitialize(),
which initializes COM to run in STA.
GeneralRe: How to make DROP of big files non-blocking ?memberBenjamin.Mayrargue14 May '02 - 21:39 
Ok I've implemented IAsyncOperation and it works nearly wonderfully.
The data transfer is done in a thread of the process where the drop occurs.
I only have one - big - problem:
 
The "EndOperation" method of IAsyncOperation is never called:
Release() and OnFinalRelease() are never called on the data object
and the data is never freed.
 
Maybe it's because of the MFC class ? (I derived from COleDataSource and just add the IAsyncOperation interface to it).
 
Thks,
B.

GeneralDrag from Explorer to My appmemberPreetham25 Mar '02 - 18:38 
I am trying to implement a drag from Windows Explorer onto the window that i opend in my prog. I am running VC++ on win2k.
When the drop on my window occurs, all i want is to popup a message that would display the File Name that i dragged.
I have written some code n so far, the display only seems to get get me a Blank.
I dont know what kind of FORMATETC values i should set for my kinda operation
GeneralRe: Drag from Explorer to My appmemberAnonymous6 May '02 - 7:19 
I too want the same stuff. ....from explorer to my application...I ned to know what file i draged so as to copy to another location specific to my app.
 
any help is invited
 
reg
Philip

GeneralRe: Drag from Explorer to My appmemberJim Crafton31 May '02 - 3:26 
The clipboard format is CF_HDROP
hte FORMATETC you want is something like this:
 
FORMATETC.dwAspect = DVASPECT_CONTENT
FORMATETC.lindex = -1
FORMATETC.ptd = NULL
FORMATETC.tymed = TYMED_HGLOBAL
FORMATETC.cfFormat = CF_HDROP
 

get the IDataObject from the OLE Clipboard
call IDataObject::GetData passing in a FORMATETC (filled in like above) and a pointer to a
STGMEDIUM struct.
The STGMEDIUM.hGlobal is what you would pass in to the following function, along with a
vector of strings
 
I beleive this should work
HRESULT COMUtils::getPidlsFromHGlobal(const HGLOBAL HGlob, std::vector<std::string>& fileNames  )
{
	LPIDA pCIDA = NULL;
	HRESULT result = E_FAIL;
 
	pCIDA = LPIDA(GlobalLock(HGlob));
 
	fileNames.clear();
 
	int count = pCIDA->cidl;
	for (int i=0;i < count; i++){
		 // [0]: folder IDList, [1] to [cidl]: item IDList
		LPCITEMIDLIST pidlf = NULL;
		pidlf = (LPCITEMIDLIST)( ((UINT)pCIDA) + pCIDA->aoffset[0]);
 
		char pathf[MAX_PATH] = "";
		SHGetPathFromIDList(pidlf, pathf);
		
		String fixedPath( pathf );		
 
		LPCITEMIDLIST pidl = NULL;
		pidl = (LPCITEMIDLIST)( ((UINT)pCIDA) + pCIDA->aoffset[i+1]);
 
		char path[MAX_PATH] = "";
		SHGetPathFromIDList(pidl, path);
		String pidlPath( path );
		int pos = pidlPath.find_last_of( "\\"); 
		if ( pos != 0 ){
			int strLength = pidlPath.length();
			strLength -= pos;
			String subStr = pidlPath.substr( pos, strLength );
			if ( (subStr != "") && (subStr.length() > 0) ){
				fixedPath += subStr;				
				fileNames.push_back( fixedPath );				
				result = S_OK;
			}
		}			
	}
	GlobalUnlock(HGlob);
	
	return result;
}

GeneralRe: Drag from Explorer to My appsussAnonymous24 Feb '03 - 0:38 
You can handle ON_WM_DROPFILES() signal which calls OnDropFiles() function with HDROP as a parameter. Then you can use DragQueryFile() function to get the number of files that were droppped into your app window and also the paths of the files that were dropped.
GeneralCant compile the projectmemberpeterboheme18 Mar '02 - 22:09 
hi,
 
i cant compile your project.
Here is the error which occurs:
fatal error RC1015: cannot open include file 'atlres.h'
'atlres.h' is included from dragdrop.rc
 
Peter
GeneralRe: Cant compile the projectmemberLeon Finker19 Mar '02 - 5:54 
The sample is written with "Windows Template Library WTL 3.1":
 
http://msdn.microsoft.com/downloads/sample.asp?url=/msdn-files/027/001/586/msdncompositedoc.xml
 

The source itself doesn't depend on it.

Questionhow to save a CF_DIB type data to my file?memberhury16 Dec '01 - 22:55 
i want to save some picture to a file ,but i don't how to do with the droped data ,who can tell me ?
Generalit's good :) but one things:((memberAnonymous27 Jun '01 - 23:55 

I want use our class, but on Winwows NT4.
Any idea to do?
GeneralRe: it's good :) but one things:((memberLeon Finker4 Jul '01 - 4:57 
Hi,
It should be usable under NT4.
Let me know.
General2 thingsmemberLeon Finker7 Jun '01 - 7:33 
1. ::CopyStgMedium can be used instead of ::OleDuplicateData. But it requires ie4.0.
So I used ::OleDuplicateData to make it more generic.
http://msdn.microsoft.com/library/psdk/com/ofn_oa2k_61gh.htm
http://msdn.microsoft.com/workshop/networking/moniker/reference/functions/CopyStgMedium.asp
 
2. ::CreateFormatEnumerator can be used instead of custom implementation of IEnumFORMATETC. It also
depends at least on ie3 to be installed.
http://msdn.microsoft.com/library/psdk/com/ofn_a2o_7zn6.htm

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 5 Jun 2001
Article Copyright 2001 by Leon Finker
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid