Implementing Drag and Drop
You may implement drag sources, drag targets, or both in your application.
Implementing a Drag Source
To implement a drag sourcethat is, to provide the data that may be dragged by the user to a targetyou use an instance of the wxDropSource class. Note that the following describes what happens after your application has decided that a drag is startingthe logic to detect the mouse motion that indicates the start of a drag is left entirely up to the application. Some controls help you by generating an event when dragging is starting, so you don't have to code the logic yourself (which could potentially interfere with the native mouse behavior for the control). This chapter provides a summary of when wxWidgets notifies you of the start of a drag.The following steps are involved, as seen from the perspective of the drop source.
1 Preparation
First of all, a data object must be created and initialized with the data you want to drag. For example:
wxTextDataObject myData(wxT("This text will be dragged."));
2 Drag Start
To start the dragging process, typically in response to a mouse click, you must create a wxDropSource object and call wxDropSource::DoDragDrop, like this:
The flags you can pass to DoDragDrop are listed in Table 11-2.
wxDropSource dragSource(this);
dragSource.SetData(myData);
wxDragResult result = dragSource.DoDragDrop(wxDrag_AllowMove);
wxDrag_CopyOnly | Only allow copying. |
wxDrag_AllowMove | Allow moving. |
wxDrag_DefaultMove | The default operation is to move the data. |
3 Dragging
The call to DoDragDrop blocks the program until the user releases the mouse button (unless you override the GiveFeedback function to do something special). When the mouse moves in a window of a program that understands the same drag and drop protocol, the corresponding wxDropTarget methods are calledsee the following section, "Implementing a Drop Target."
4 Processing the Result
DoDragDrop returns an effect code, which is one of the values of the wxDragResult type, as listed in Table 11-3.
wxDragError | An error prevented the drag and drop operation from completing. |
wxDragNone | The drop target didn't accept the data. |
wxDragCopy | The data was successfully copied. |
wxDragMove | The data was successfully moved (Windows only). |
wxDragLink | This was a link operation. |
wxDragCancel | The user canceled the operation. |
Here's an example showing how to implement a text drop source. DnDWindow contains a member variable m_strTextwhen the left mouse button is clicked, a drag operation is started using the value of m_strText. The result of the drag operation is reported in a message box. In practice, the drag operation wouldn't be started until the pointer has been dragged a minimum distance so that simple left-click actions can be distinguished from a drag.
switch (result)
{
case wxDragCopy: /* Data was copied or linked:
do nothing special */
case wxDragLink:
break;
case wxDragMove: /* Data was moved: delete original */
DeleteMyDraggedData();
break;
default: /* Drag was cancelled, or the data was
not accepted, or there was an error:
do nothing */
break;
}
void DnDWindow::OnLeftDown(wxMouseEvent& event )
{
if ( !m_strText.IsEmpty() )
{
// start drag operation
wxTextDataObject textData(m_strText);
wxDropSource source(textData, this,
wxDROP_ICON(dnd_copy),
wxDROP_ICON(dnd_move),
wxDROP_ICON(dnd_none));
int flags = 0;
if ( m_moveByDefault )
flags |= wxDrag_DefaultMove;
else if ( m_moveAllow )
flags |= wxDrag_AllowMove;
wxDragResult result = source.DoDragDrop(flags);
const wxChar *pc;
switch ( result )
{
case wxDragError: pc = wxT("Error!"); break;
case wxDragNone: pc = wxT("Nothing"); break;
case wxDragCopy: pc = wxT("Copied"); break;
case wxDragMove: pc = wxT("Moved"); break;
case wxDragCancel: pc = wxT("Cancelled"); break;
default: pc = wxT("Huh?"); break;
}
wxMessageBox(wxString(wxT("Drag result: ")) + pc);
}
}
Implementing a Drop Target
To implement a drop targetthat is, to receive the data dropped by the useryou associate a wxDropTarget object with a window using wxWindow::SetDrop Target. You must derive your own class from wxDropTarget and override its pure virtual methods. In particular, override OnDragOver to return a wxDragResult code indicating how the cursor should change when it's over the given point in the window, and override OnData to react to the drop. Alternatively, you may derive from wxTexTDropTarget or wxFileDropTarget and override their OnDropText or OnDropFiles method.The following steps happen in a drag and drop operation from the perspective of a drop target.
1 Initialization
wxWindow::SetDropTarget is called during window creation to associate the drop target with the window. At creation, or during subsequent program execution, a data object is associated with the drop target using wxDropTarget:: SetDataObject. This data object will be responsible for the format negotiation between the drag source and the drop target.
2 Dragging
As the mouse moves over the target during a drag operation, wxDropTarget:: OnEnter, wxDropTarget::OnDragOver and wxDropTarget::OnLeave are called as appropriate, each returning a suitable wxDragResult so that the drag implementation can give suitable visual feedback.
3 Drop
When the user releases the mouse button over a window, wxWidgets asks the associated wxDropTarget object if it accepts the data by calling wxDataObject::GetAllFormats. If the data type is accepted, then wxDrop Target::OnData will be called, and the wxDataObject belonging to the drop target can be filled with data. wxDropTarget::OnData returns a wxDragResult, which is then returned from wxDropSource::DoDragDrop.
Using Standard Drop Targets
wxWidgets provides classes derived from wxDropTarget so that you don't have to program everything yourself for commonly used cases. You just derive from the class and override a virtual function to get notification of the drop.wxTextdropTarget receives dropped textjust override OnDropText to do something with the dropped text. The following example implements a drop target that appends dropped text to a list box.
The next example shows how to use wxFileDropTarget, which accepts files dropped from the system's file manager (such as Explorer on Windows) and reports the number of files dropped and their names.
// A drop target that adds text to a listbox
class DnDText : public wxTextDropTarget
{
public:
DnDText(wxListBox *owner) { m_owner = owner; }
virtual bool OnDropText(wxCoord x, wxCoord y,
const wxString& text)
{
m_owner->Append(text);
return true;
}
private:
wxListBox *m_owner;
};
// Set the drop target
wxListBox* listBox = new wxListBox(parent, wxID_ANY);
listBox->SetDropTarget(new DnDText(listBox));
// A drop target that adds filenames to a list box
class DnDFile : public wxFileDropTarget
{
public:
DnDFile(wxListBox *owner) { m_owner = owner; }
virtual bool OnDropFiles(wxCoord x, wxCoord y,
const wxArrayString& filenames)
{
size_t nFiles = filenames.GetCount();
wxString str;
str.Printf( wxT("%d files dropped"), (int) nFiles);
m_owner->Append(str);
for ( size_t n = 0; n < nFiles; n++ ) {
m_owner->Append(filenames[n]);
}
return true;
}
private:
wxListBox *m_owner;
};
// Set the drop target
wxListBox* listBox = new wxListBox(parent, wxID_ANY);
listBox->SetDropTarget(new DnDFile(listBox));
Creating a Custom Drop Target
Now we'll create a custom drop target that can accept URLs (web addresses). This time we need to override OnData and OnDragOver, and we introduce a virtual function OnDropURL that derived classes can override.
// A custom drop target that accepts URL objects
class URLDropTarget : public wxDropTarget
{
public:
URLDropTarget() { SetDataObject(new wxURLDataObject); }
void OnDropURL(wxCoord x, wxCoord y, const wxString& text)
{
// Of course, a real program would do something more
// useful here...
wxMessageBox(text, wxT("URLDropTarget: got URL"),
wxICON_INFORMATION | wxOK);
}
// URLs can't be moved, only copied
virtual wxDragResult OnDragOver(wxCoord x, wxCoord y,
wxDragResult def)
{
return wxDragLink;
}
// translate this to calls to OnDropURL() just for convenience
virtual wxDragResult OnData(wxCoord x, wxCoord y,
wxDragResult def)
{
if ( !GetData() )
return wxDragNone;
OnDropURL(x, y, ((wxURLDataObject *)m_dataObject)->GetURL());
return def;
}
};
// Set the drop target
wxListBox* listBox = new wxListBox(parent, wxID_ANY);
listBox->SetDropTarget(new URLDropTarget);
More on wxDataObject
As we've seen, a wxDataObject represents data that can be copied to or from the clipboard or dragged and dropped. The important thing about wxDataObject is that it is a "smart" piece of data, unlike the usual dumb data containers such as memory buffers or files. Being "smart" means that the data object itself should know what data formats it supports and how to render itself in each of its supported formats.A supported format is the format in which the data can be requested from a data object or from which the data object may be set. In the general case, an object may support different formats on input and output, so it may be able to render itself in a given format but not be created from data in this format, or vice versa.Several solutions are available to you when you need to use a wxDataObject class:
- Use one of the built-in classes.
You may use wxTextdataObject, wxBitmap DataObject, or wxFileDataObject in the simplest cases when you only need to support one format and your data is text, a bitmap, or a list of files. - Use wxDataObjectSimple.
Deriving from wxDataObjectSimple is the simplest solution for custom datayou will only support one format, and so you probably won't be able to communicate with other programs, but data transfer will work in your program (or between different copies of it). - Derive from wxCustomDataObject (a subclass of wxDataObjectSimple) for user-defined formats.
- Use wxDataObjectComposite.
This is a simple but powerful solution that allows you to support any number of formats (either standard or custom if you combine it with the previous solutions). - Use wxDataObject directly.
This is the solution for maximum flexibility and efficiency, but it is also the most difficult to implement.
Deriving from wxDataObject
Let's look at what's needed to derive a new class from wxDataObject. Deriving from the other classes mentioned earlier is similar but easier, so we won't cover them all here.Each class derived directly from wxDataObject must override and implement all of its functions that are pure virtual in the base class. Data objects that only render their data or only set it (that is, work in only one direction) should return 0 from GetFormatCount for the unsupported direction.GetAllFormats takes an array of wxDataFormat values and a Direction (Get or Set). Copy all the supported formats in the given direction to the formats array. GetFormatCount determines the number of elements in the array.GetdataHere takes a wxDataFormat and a void* buffer, returning TRue on success and false otherwise. It must write the data for the given format into the buffer. This can be arbitrary binary or text data that need only be recognized by SetData.GetdataSize takes a wxDataFormat and returns the data size for the given format.GetFormatCount returns the number of available formats for rendering or setting the data.GetPreferredFormat takes a Direction and returns the preferred wxDataFormat for this direction.SetData takes a wxDataFormat, integer buffer length, and void* buffer. You interpret the data in the buffer in the required way for this object, such as copying it to an internal structure. This function should return TRue on success and false on failure.
The wxWidgets Drag and Drop Sample
We'll use the wxWidgets drag and drop sample in samples/dnd to demonstrate how to write a custom wxDataObject with a user-defined data format. The sample shows a simple shapea triangle, rectangle, or ellipseand allows you to edit it, drag it to a new position, and copy it to and paste it back from the clipboard. You can show the shape frame with the New Frame command on the File menu. This window is illustrated in Figure 11-1.
Figure 11-1. The wxWidgets drag and drop sample

A clipboard paste is also straightforward, calling wxClipboard::GetData to try to get shape data from the clipboard and then retrieving the shape data from the data object. We also show the UI update handler that will enable the Paste menu command only if there is shape data on the clipboard. shapeFormatId is a global variable containing the shape format name, wxShape.
void DnDShapeFrame::OnCopyShape(wxCommandEvent& event)
{
if ( m_shape )
{
wxClipboardLocker clipLocker;
if ( !clipLocker )
{
wxLogError(wxT("Can't open the clipboard"));
return;
}
wxTheClipboard->AddData(new DnDShapeDataObject(m_shape));
}
}
To implement drag and drop, a drop target class is required that will notify the application when data is dropped. Objects of class DnDShapeDropTarget contain a DnDShapeDataObject that is ready to receive data when its OnData member is called. Here's the declaration (and implementation) of DnDShapeDropTarget:
void DnDShapeFrame::OnPasteShape(wxCommandEvent& event)
{
wxClipboardLocker clipLocker;
if ( !clipLocker )
{
wxLogError(wxT("Can't open the clipboard"));
return;
}
DnDShapeDataObject shapeDataObject(NULL);
if ( wxTheClipboard->GetData(shapeDataObject) )
{
SetShape(shapeDataObject.GetShape());
}
else
{
wxLogStatus(wxT("No shape on the clipboard"));
}
}
void DnDShapeFrame::OnUpdateUIPaste(wxUpdateUIEvent& event)
{
event.Enable( wxTheClipboard->
IsSupported(wxDataFormat(shapeFormatId)) );
}
The target is set when the shape frame is created during application initialization:
class DnDShapeDropTarget : public wxDropTarget
{
public:
DnDShapeDropTarget(DnDShapeFrame *frame)
: wxDropTarget(new DnDShapeDataObject)
{
m_frame = frame;
}
// override base class (pure) virtuals
virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def)
{
m_frame->SetStatusText(_T("Mouse entered the frame"));
return OnDragOver(x, y, def);
}
virtual void OnLeave()
{
m_frame->SetStatusText(_T("Mouse left the frame"));
}
virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def)
{
if ( !GetData() )
{
wxLogError(wxT("Failed to get drag and drop data"));
return wxDragNone;
}
// Notify the frame of the drop
m_frame->OnDrop(x, y,
((DnDShapeDataObject *)GetDataObject())->GetShape());
return def;
}
private:
DnDShapeFrame *m_frame;
};
A drag starts when a left mouse button click is detected, and the event handler creates a wxDropSource passing a DnDShapeDataObject before calling DoDragDrop to initiate the drag operation. DndShapeFrame::OnDrag looks like this:
DnDShapeFrame::DnDShapeFrame(wxFrame *parent)
: wxFrame(parent, wxID_ANY, _T("Shape Frame"))
{
...
SetDropTarget(new DnDShapeDropTarget(this));
...
}
When the drop is signaled by the user releasing the mouse button, wxWidgets calls DnDShapeDropTarget::OnData, which in turn calls DndShapeFrame::OnDrop with a new DndShape to set at the drop position. This completes the drag and drop operation.
void DnDShapeFrame::OnDrag(wxMouseEvent& event)
{
if ( !m_shape )
{
event.Skip();
return;
}
// start drag operation
DnDShapeDataObject shapeData(m_shape);
wxDropSource source(shapeData, this);
const wxChar *pc = NULL;
switch ( source.DoDragDrop(true) )
{
default:
case wxDragError:
wxLogError(wxT("An error occured during drag and drop"));
break;
case wxDragNone:
SetStatusText(_T("Nothing happened"));
break;
case wxDragCopy:
pc = _T("copied");
break;
case wxDragMove:
pc = _T("moved");
if ( ms_lastDropTarget != this )
{
// don't delete the shape if we dropped it
// on ourselves!
SetShape(NULL);
}
break;
case wxDragCancel:
SetStatusText(_T("Drag and drop operation cancelled"));
break;
}
if ( pc )
{
SetStatusText(wxString(_T("Shape successfully ")) + pc);
}
//else: status text already set
}
The only remaining tricky bit is to implement the custom wxDataObject. We'll show the implementation in parts for clarity. First, we'll see the custom format identifier declaration, the DndShapeDataObject class declaration, its constructor and destructor, and its data members.The format identifier is shapeFormatId, and it is a global variable used throughout the sample. The constructor takes a new copy of the shape (if one is passed) by using GeTDataHere; the copy could also have been implemented by using a DndShape::Clone function, had one been provided. The DnDShapeData Object destructor will delete this shape object.DndShapeDataObject can provide bitmap and (on supported platforms) metafile renderings of its shape, so it also has wxBitmapDataObject and wxMetaFileDataObject members (and associated flags to indicate whether they're valid) to cache these formats when asked for them.
void DnDShapeFrame::OnDrop(wxCoord x, wxCoord y, DnDShape *shape)
{
ms_lastDropTarget = this;
wxPoint pt(x, y);
wxString s;
s.Printf(wxT("Shape dropped at (%d, %d)"), pt.x, pt.y);
SetStatusText(s);
shape->Move(pt);
SetShape(shape);
}
Next, let's look at the functions that need to be provided to answer questions about the data that the object provides. GetPreferredFormat simply returns the "native" format for this object, m_formatShape, which we initialized with wxShape in the constructor. GetFormatCount determines whether the custom format can be used for setting and getting databitmap and metafile formats can only be handled when getting data. GeTDataSize returns a suitable size depending on what kind of data is requested, if necessary creating the data in bitmap or metafile format in order to find out its size.
// Custom format identifier
static const wxChar *shapeFormatId = wxT("wxShape");
class DnDShapeDataObject : public wxDataObject
{
public:
// ctor doesn't copy the pointer, so it shouldn't go away
// while this object is alive
DnDShapeDataObject(DnDShape *shape = (DnDShape *)NULL)
{
if ( shape )
{
// we need to copy the shape because the one
// we've handled may be deleted while it's still on
// the clipboard (for example) - and we reuse the
// serialisation methods here to copy it
void *buf = malloc(shape->DnDShape::GetDataSize());
shape->GetDataHere(buf);
m_shape = DnDShape::New(buf);
free(buf);
}
else
{
// nothing to copy
m_shape = NULL;
}
// this string should uniquely identify our format, but
// is otherwise arbitrary
m_formatShape.SetId(shapeFormatId);
// we don't draw the shape to a bitmap until it's really
// needed (i.e. we're asked to do so)
m_hasBitmap = false;
m_hasMetaFile = false;
}
virtual ~DnDShapeDataObject() { delete m_shape; }
// after a call to this function, the shape is owned by the
// caller and it is responsible for deleting it
DnDShape *GetShape()
{
DnDShape *shape = m_shape;
m_shape = (DnDShape *)NULL;
m_hasBitmap = false;
m_hasMetaFile = false;
return shape;
}
// The other member functions omitted
...
// The data members
private:
wxDataFormat m_formatShape; // our custom format
wxBitmapDataObject m_dobjBitmap; // it handles bitmaps
bool m_hasBitmap; // true if m_dobjBitmap valid
wxMetaFileDataObject m_dobjMetaFile;// handles metafiles
bool m_hasMetaFile;// true if MF valid
DnDShape *m_shape; // our data
};
GetdataHere copies data into a void* buffer, again depending on what format is requested, as follows:
virtual wxDataFormat GetPreferredFormat(Direction dir) const
{
return m_formatShape;
}
virtual size_t GetFormatCount(Direction dir) const
{
// our custom format is supported by both GetData()
// and SetData()
size_t nFormats = 1;
if ( dir == Get )
{
// but the bitmap format(s) are only supported for output
nFormats += m_dobjBitmap.GetFormatCount(dir);
nFormats += m_dobjMetaFile.GetFormatCount(dir);
}
return nFormats;
}
virtual void GetAllFormats(wxDataFormat *formats, Direction dir) const
{
formats[0] = m_formatShape;
if ( dir == Get )
{
// in Get direction we additionally support bitmaps and metafiles
//under Windows
m_dobjBitmap.GetAllFormats(&formats[1], dir);
// don't assume that m_dobjBitmap has only 1 format
m_dobjMetaFile.GetAllFormats(&formats[1 +
m_dobjBitmap.GetFormatCount(dir)], dir);
}
}
virtual size_t GetDataSize(const wxDataFormat& format) const
{
if ( format == m_formatShape )
{
return m_shape->GetDataSize();
}
else if ( m_dobjMetaFile.IsSupported(format) )
{
if ( !m_hasMetaFile )
CreateMetaFile();
return m_dobjMetaFile.GetDataSize(format);
}
else
{
wxASSERT_MSG( m_dobjBitmap.IsSupported(format),
wxT("unexpected format") );
if ( !m_hasBitmap )
CreateBitmap();
return m_dobjBitmap.GetDataSize();
}
}
SetData only deals with the native format, so all it has to do is call DndShape::New to make a shape out of the supplied buffer:
virtual bool GetDataHere(const wxDataFormat& format, void *pBuf) const
{
if ( format == m_formatShape )
{
// Uses a ShapeDump struct to stream itself to void*
m_shape->GetDataHere(pBuf);
return true;
}
else if ( m_dobjMetaFile.IsSupported(format) )
{
if ( !m_hasMetaFile )
CreateMetaFile();
return m_dobjMetaFile.GetDataHere(format, pBuf);
}
else
{
wxASSERT_MSG( m_dobjBitmap.IsSupported(format),
wxT("unexpected format") );
if ( !m_hasBitmap )
CreateBitmap();
return m_dobjBitmap.GetDataHere(pBuf);
}
}
The way that DndShape serializes itself in and out of a void* buffer is quite straightforward: it uses a ShapeDump structure that stores the shape's details. Here's how:
virtual bool SetData(const wxDataFormat& format,
size_t len, const void *buf)
{
wxCHECK_MSG( format == m_formatShape, false,
wxT( "unsupported format") );
delete m_shape;
m_shape = DnDShape::New(buf);
// the shape has changed
m_hasBitmap = false;
m_hasMetaFile = false;
return true;
}
Finally, going back to the DnDShapeDataObject class, the functions that create data in metafile and bitmap formats when required look like this:
// Static function that creates a shape from a void* buffer
DnDShape *DnDShape::New(const void *buf)
{
const ShapeDump& dump = *(const ShapeDump *)buf;
switch ( dump.k )
{
case Triangle:
return new DnDTriangularShape(
wxPoint(dump.x, dump.y),
wxSize(dump.w, dump.h),
wxColour(dump.r, dump.g, dump.b));
case Rectangle:
return new DnDRectangularShape(
wxPoint(dump.x, dump.y),
wxSize(dump.w, dump.h),
wxColour(dump.r, dump.g, dump.b));
case Ellipse:
return new DnDEllipticShape(
wxPoint(dump.x, dump.y),
wxSize(dump.w, dump.h),
wxColour(dump.r, dump.g, dump.b));
default:
wxFAIL_MSG(wxT("invalid shape!"));
return NULL;
}
}
// Gets the data size
size_t DndShape::GetDataSize() const
{
return sizeof(ShapeDump);
}
// Serialises into a void* buffer
void DndShape::GetDataHere(void *buf) const
{
ShapeDump& dump = *(ShapeDump *)buf;
dump.x = m_pos.x;
dump.y = m_pos.y;
dump.w = m_size.x;
dump.h = m_size.y;
dump.r = m_col.Red();
dump.g = m_col.Green();
dump.b = m_col.Blue();
dump.k = GetKind();
}
Our custom data object implementation is now complete, apart from the details of how shapes draw themselves and the code to create GUI. For these details, please see the drag and drop sample source in samples/dnd in your wxWidgets distribution.
void DnDShapeDataObject::CreateMetaFile() const
{
wxPoint pos = m_shape->GetPosition();
wxSize size = m_shape->GetSize();
wxMetaFileDC dcMF(wxEmptyString, pos.x + size.x, pos.y + size.y);
m_shape->Draw(dcMF);
wxMetafile *mf = dcMF.Close();
DnDShapeDataObject *self = (DnDShapeDataObject *)this;
self->m_dobjMetaFile.SetMetafile(*mf);
self->m_hasMetaFile = true;
delete mf;
}
void DnDShapeDataObject::CreateBitmap() const
{
wxPoint pos = m_shape->GetPosition();
wxSize size = m_shape->GetSize();
int x = pos.x + size.x,
y = pos.y + size.y;
wxBitmap bitmap(x, y);
wxMemoryDC dc;
dc.SelectObject(bitmap);
dc.SetBrush(wxBrush(wxT("white"), wxSOLID));
dc.Clear();
m_shape->Draw(dc);
dc.SelectObject(wxNullBitmap);
DnDShapeDataObject *self = (DnDShapeDataObject *)this;
self->m_dobjBitmap.SetBitmap(bitmap);
self->m_hasBitmap = true;
}
Drag and Drop Helpers in wxWidgets
Here are some of the controls that give you a helping hand when implementing drag and drop.
wxTreeCtrl
You can use the EVT_trEE_BEGIN_DRAG or EVT_TREE_BEGIN_RDRAG event table macros to intercept the start of left or right dragging, as determined by the internal tree control mouse-handling code. In your handler for the start of the drag, call wxtreeEvent::Allow if you want wxtreeCtrl to use its own drag implementation and send an EVT_trEE_END_DRAG event. If you elect to use the tree control's implementation for dragging, a drag image will be created and moved as the mouse is dragged around the tree control. The drop behavior is determined entirely by application code in the "end drag" handler.The following example shows how to use the tree control's drag and drop events. When the user drags and drops an item onto another item, a copy will be appended after the second item.
If you want to handle a drag operation your own way, for example using wxDropSource, you can omit the wxtreeEvent::Allow call in the drag start handler and start the drag operation using your chosen method. The tree drag end event will not be sent because it's up to your code to decide how the drag ends (if using wxDropSource::DoDragDrop, the drag end detection is handled for you).
BEGIN_EVENT_TABLE(MyTreeCtrl, wxTreeCtrl)
EVT_TREE_BEGIN_DRAG(TreeTest_Ctrl, MyTreeCtrl::OnBeginDrag)
EVT_TREE_END_DRAG(TreeTest_Ctrl, MyTreeCtrl::OnEndDrag)
END_EVENT_TABLE()
void MyTreeCtrl::OnBeginDrag(wxTreeEvent& event)
{
// need to explicitly allow drag
if ( event.GetItem() != GetRootItem() )
{
m_draggedItem = event.GetItem();
wxLogMessage(wxT("OnBeginDrag: started dragging %s"),
GetItemText(m_draggedItem).c_str());
event.Allow();
}
else
{
wxLogMessage(wxT("OnBeginDrag: this item can't be dragged."));
}
}
void MyTreeCtrl::OnEndDrag(wxTreeEvent& event)
{
wxTreeItemId itemSrc = m_draggedItem,
itemDst = event.GetItem();
m_draggedItem = (wxTreeItemId)0l;
// where to copy the item?
if ( itemDst.IsOk() && !ItemHasChildren(itemDst) )
{
// copy to the parent then
itemDst = GetItemParent(itemDst);
}
if ( !itemDst.IsOk() )
{
wxLogMessage(wxT("OnEndDrag: can't drop here."));
return;
}
wxString text = GetItemText(itemSrc);
wxLogMessage(wxT("OnEndDrag: '%s' copied to '%s'."),
text.c_str(), GetItemText(itemDst).c_str());
// append the item here
int image = wxGetApp().ShowImages() ? TreeCtrlIcon_File : -1;
AppendItem(itemDst, text, image);
}
wxListCtrl
This class doesn't provide any default drag image or an end of drag notification, but it does let you know when to start a drag operation for an item; use the EVT_LIST_BEGIN_DRAG or EVT_LIST_BEGIN_RDRAG event table macros and implement your own drag and drop code. You can also detect when column dividers are being dragged by using EVT_LIST_COL_BEGIN_DRAG, EVT_LIST_COL_DRAGGING and EVT_LIST_COL_END_DRAG.
wxDragImage
wxDragImage is a handy class to use when implementing your own drag and drop; it draws an image on top of a window, and it provides methods to move the image without damaging the window underneath. The generic implementation does this by saving a copy of the underlying window and repainting it along with the image when necessary.Figure 11-2 shows the main window of the wxDragImage sample, which you can find in samples/dragimag in your wxWidgets distribution. When the three puzzle shapes are dragged, a different drag image is used for each: the shape itself, an icon, and an image dynamically created out of a text string. If you check Use Whole Screen for Dragging, then the shape may be dragged outside of the window. On Windows, the sample may be compiled using either the generic wxDragImage implementation (the default), or the native class by setting the value of wxUSE_GENERIC_DRAGIMAGE to 1 in dragimag.cpp.
Figure 11-2. The wxDragImage sample

If you want to draw the image yourself during the drag, instead of just passing a bitmap as the dragged image, use wxGenericDragImage and override wxDragImage::DoDrawImage and wxDragImage::GetImageRect. wxDragImage is an alias for wxGenericDragImage on non-Windows platforms. The Windows implementation doesn't support DoDrawImage and is also limited to drawing rather ghostly translucent images, so you will probably want to use wxGenericDragImage on all platforms.
void MyCanvas::OnMouseEvent(wxMouseEvent& event)
{
if (event.LeftDown())
{
DragShape* shape = FindShape(event.GetPosition());
if (shape)
{
// We tentatively start dragging, but wait for
// mouse movement before dragging properly.
m_dragMode = TEST_DRAG_START;
m_dragStartPos = event.GetPosition();
m_draggedShape = shape;
}
}
else if (event.LeftUp() && m_dragMode != TEST_DRAG_NONE)
{
// Finish dragging
m_dragMode = TEST_DRAG_NONE;
if (!m_draggedShape || !m_dragImage)
return;
m_draggedShape->SetPosition(m_draggedShape->GetPosition()
+ event.GetPosition() - m_dragStartPos);
m_dragImage->Hide();
m_dragImage->EndDrag();
delete m_dragImage;
m_dragImage = NULL;
m_draggedShape->SetShow(true);
m_draggedShape->Draw(dc);
m_draggedShape = NULL;
}
else if (event.Dragging() && m_dragMode != TEST_DRAG_NONE)
{
if (m_dragMode == TEST_DRAG_START)
{
// We will start dragging if we've moved beyond a
// couple of pixels
int tolerance = 2;
int dx = abs(event.GetPosition().x - m_dragStartPos.x);
int dy = abs(event.GetPosition().y - m_dragStartPos.y);
if (dx <= tolerance && dy <= tolerance)
return;
// Start the drag.
m_dragMode = TEST_DRAG_DRAGGING;
if (m_dragImage)
delete m_dragImage;
// Erase the dragged shape from the canvas
m_draggedShape->SetShow(false);
wxClientDC dc(this);
EraseShape(m_draggedShape, dc);
DrawShapes(dc);
m_dragImage = new wxDragImage(
m_draggedShape-
//>GetBitmap());
// The offset between the top-left of the shape image and
// the current shape position
wxPoint beginDragHotSpot = m_dragStartPos
m_draggedShape-
//>GetPosition();
// Always assume coordinates relative to the capture
// window (client coordinates)
if (!m_dragImage->BeginDrag(beginDragHotSpot, this))
{
delete m_dragImage;
m_dragImage = NULL;
m_dragMode = TEST_DRAG_NONE;
} else
{
m_dragImage->Move(event.GetPosition());
m_dragImage->Show();
}
}
else if (m_dragMode == TEST_DRAG_DRAGGING)
{
// Move the image
m_dragImage->Move(event.GetPosition());
}
}
}
