Drag and Drop Between Any CWnd-Derived Window
Why this code?
Suppose you have a number or controls on various windows that you want to drag and drop data between. Now suppose that the data shown in your controls come from your own data objects, so that dragging and dropping is not only required for text by also your own objects. Enter this code.
Before we get into it, I want to acknowledge contributions made by Microsoft's knowledge base article Q135299.
If you hate reading and want to implement something and learn it later, then skip to the implementation section. If you want a warmer and fuzzier feeling read the theory section.
THEORYHow OLE drag and drop works
Drag and Drop (D&D) works similar to the old Win3.1 clipboard cut and paste. In essence when someone initiates a drag operation (usually the WM_LBUTTONDOWN operation), data is copied to global memory in a specified format.
When the mouse is over a window that has been registered as a drop target accepting the format delivered to the global memory, the user can release the mouse and the framework will receive the drop operation.
MFC has provided COleDataSource and COleDataTarget to handle this functionality.
Enabling a window to initiate a Drag and Drop Operation
Now let's look at the specifics for enabling D&D using MFC. In the D&D initiating event for a particular control we need to do the following:
- 1. Instantiate a COleDataSource object
Most likely you will need to derive a class from the control you want to D&D enable. Your class needs to have a COleDataSource object. With something like:
- 2. Create a global memory space to hold our data.
If the data we are sending is simple text then use
HGLOBAL hgData=GlobalAlloc(GPTR,10 ); //The 10 is the data length
If you want to send something else use
//The 10 is the data length HGLOBAL hgData=GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT ,10 );
- 3. Lock the memory so something is won't move or be deleted:
- 4. Put the data into the global memory
After wresting with these formats at infinitum, I had to cheat when moving objects other than text. Since I don't want to create a copy of my object, I simply need to convey the address of the memory where my object is located.
So, if I have an instance of CMyObject1 (for example somewhere previously I created it with something like Data = new CMyObject1();), then I can store the address of the object with:
ltoa( Data, (char*)lpData, 10 );
Of course if I am dragging just text, then all I need to do is fill lpData with the text I want to drag.
- 5. Put the global data into the COleDropTarget bus. The format of the data that you choose to put into the OLE object will determine what windows can accept it. Text is the most commonly used format, so putting your data in the OLE object as Text will allow it to be dropped onto a wide variety of D&D drop enabled windows. (These windows can be any D&D enabled window in any application)
For simple text use:
In my case, however, I have put the memory address of the object and therefore don't want other applications to accept a drop of some number. So I will use a kind of owner-drawn format. You can also register your own format with RegisterClipBoardFormat, but I could never get this to work. So for our object example use:
- 6. Start the drag and drop The drag and drop function will only start with DoDragDrop and when the mouse moves out of the coordinates specified in the last parameter. So we start the D&D operation with:
DROPEFFECT dropEffect = m_Source.DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE, (LPCRECT)rClient);
- 7. Do something with our source control If this is a move operation, we need to do something with the data in the source control (like delete the string from a listbox or clear out an edit box. After the mouse is released, program control will return to the calling function and therefore the return of DoDragDrop can be tested with something like:
//this is your function to do something if((dropEffect&DROPEFFECT_MOVE)==DROPEFFECT_MOVE)<BR> CompleteMove();
- 8. Clean up
Lastly we need to clear out the data that was put into the COleDataSource with:
Enabling a window to accept a Drag and Drop Operation
Not all windows have to be both drag and drop enabled. Some can be just drag and others be drop.
- 1. Instantiate a COleDataTarget
Like the source, we need to provide D&D interface objects. For the drop target this is the COleDataTarget object. Add a COleDataTarget to your derived drop enabled widow with something like:
- 2. Register the window as one that is D&D enabled If you have a window instantiated with some FrameWnd then you can register the COleDataTarget member in the OnCreate method. But this won't work for Dialog boxes so you'll need to do this in the OnInitialUpdate. In either case this is done with:
- 3. Respond to the OnDrop message
At a minimum you will need to add the OnDrop method to your derived control object. You can also override the OnDragEnter(), OnDragLeave() and the OnDragOver() methods if you like. Within your OnDrop method you will need to do the following:
- 4. Get the global memory To get the data that you put into global memory use something like:
If you will recall, because I am transferring the address of my object, I formatted the data into global memory with CF_OWNERDISPLAY. Therefore I will need to get it with the same format with:
- 5. Get the data in the global memory
Get the data from global memory with:
For simple text D&D operations you can do whatever you want with this text. However, for my needs I need to translate this text into an object by typecasting the numeric version of the text into my object with like:
CMyObject1* MyObj = (CMyObject1*)atol( pData );
Now I can do whatever I want with this object.
- 6. Clean up Lastly the global memory can be clear with:
I have created two wrapper classes for the OLE stuff. The following is what you need to do to implement these classes:
- 1. In the InititInstance method you're your app, add:
- 2. Add CDropSource and CDropTarget to your project.
- 3. Derive a class for each kind of class you want to D&D enable. Add CDropSource to your class inheritance or create a CDropSource member if you want the control to initiate D&D operations. Add CDropTarget to your class inheritance or create a CDropTarget member if you want the control to accept D&D operations. (If you choose the multiple inheritance route, you need to explicitly create the new and delete operators.)
- 4. After creating your form or dialog in the resource editor and assigning the controls to variables (of type control), replace the control type in the class controlling your form with the derived control classes you just created.
- 5. In the OnInitialUpdate of the class controlling your form register the CDropTarget with m_List.Initialize( &m_List );
- 6. If you want to handle move operations and you inherited from CDropSource, then override the CompleteMove() method of CDropSource. If you created a variable use the if statement in the StartDragging method of CDropSource to test for a move operation and do what you need.
- 7. To initiate a drag operation, add the LButtonDown method of your derived controls. In that method call the StartDragging method of CDropSource.
- 8. To accept a drop, add the OnDrop method to your derived control and finish the drop operation. See the example in the OnDrop method of CdragList for an object D&D or the OnDrop method of CDropTarget for a simpler text example.
A WORD ON THE SAMPLE PROJECTThe sample project contains a Drag and Drop enabled Edit and Listbox control and a drag only enabled tree control. All the entries in the listbox have CMyObject1 object behind them. Therefore double, clicking an entry in the list will show the data contained in the object. Obviously you can whatever you want.
When the user drags text from the edit box to the list, the app will create a CMyObject1 and put that into the D&D operation.
Note too that the D&D operation will only work for my windows. This is because I put the data into global memory in my format and not something more ubiquitous like CF_TEXT.