Implementing Drag and Drop

team bbl


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:

 wxDropSource dragSource(this); dragSource.SetData(myData); wxDragResult result = dragSource.DoDragDrop(wxDrag_AllowMove); 

The flags you can pass to DoDragDrop are listed in Table 11-2.

Table 11-2. Flags for DoDragDrop

wxDrag_CopyOnly

Only allow copying.

wxDrag_AllowMove

Allow moving.

wxDrag_DefaultMove

The default operation is to move the data.


When creating the wxDropSource object, you have the option of also specifying the window that initiates the drag, and three cursors for Copy, Move, and Can't Drop feedback. These are actually icons in GTK+ and cursors on other platforms, so the macro wxDROP_ICON can be used to hide this difference, as we'll see in our text drop example shortly.

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.

Table 11-3. wxDragResult Return Types from DoDragDrop

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.


Respond to the result of DoDragDrop appropriately in your application. If the return value was wxDragMove, it's normal to delete the data associated with the drop source and update the display. A return value of wxDragNone means that the drag was cancelled. For example:

 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; } 

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.

 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.

 // 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)); 

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 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:

  1. 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.

  2. 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).

  3. Derive from wxCustomDataObject (a subclass of wxDataObjectSimple) for user-defined formats.

  4. 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).

  5. Use wxDataObject directly. This is the solution for maximum flexibility and efficiency, but it is also the most difficult to implement.

The easiest way to use drag and drop and the clipboard with multiple formats is by using wxDataObjectComposite, but it is not the most efficient one because each wxDataObjectSimple contains all the data in its specific format. Imagine that you want to paste to the clipboard 200 pages of text in your proprietary format, as well as Word, RTF, HTML, Unicode, and plain text. Even today's computers would struggle to support this task. For efficiency, you will have to derive from wxDataObject directly, make it enumerate its formats, and provide the data in the requested format on demand.

The underlying data transfer mechanisms for clipboard and drag and drop don't copy any data until another application actually requests it. So although a user may think that the data resides in the clipboard after pressing the application's Copy command, in reality it may only have been declared to be available.

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


The shapes are modeled with classes derived from DnDShape, and the data object is called DnDShapeDataObject. Before examining the implementation of DndShapeDataObject, let's see how the application will use them.

When a clipboard copy operation is invoked, a DnDShapeDataObject will be added to the clipboard, taking a copy of the current shape in case it's deleted while the object is still on the clipboard. Here's the code to do it:

 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));     } } 

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::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)) ); } 

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:

 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; }; 

The target is set when the shape frame is created during application initialization:

 DnDShapeFrame::DnDShapeFrame(wxFrame *parent)              : wxFrame(parent, wxID_ANY, _T("Shape Frame")) {     ...     SetDropTarget(new DnDShapeDropTarget(this));     ... } 

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:

 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 } 

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::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); } 

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.

 // 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 }; 

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.

 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();     } } 

GetdataHere copies data into a void* buffer, again depending on what format is requested, as follows:

 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);     } } 

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 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; } 

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:

 // 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(); } 

Finally, going back to the DnDShapeDataObject class, the functions that create data in metafile and bitmap formats when required look like this:

 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; } 

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.

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.

 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); } 

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).

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


When the start of a drag operation is detected, create a wxDragImage object and store it somewhere that you can access it as the drag progresses. Call BeginDrag to start and EndDrag to stop the drag. To move the image, initially call Show and then Move. If you want to update the screen contents during the drag (for example, highlight an item as in the dragimag sample), call Hide, update the window, call Move, and then call Show.

You can drag within one window, or you can use full-screen dragging either across the whole screen or across just one area of it to save resources. If you want the user to drag between two windows with different top-level parents, then you will need to use full-screen dragging. Full-screen dragging is not ideal because it takes a snapshot of the screen at the start of the drag and doesn't take into account changes that are happening in other applications during the drag.

In the following example, based on the sample illustrated in Figure 11-2, MyCanvas displays a number of shapes of class DragShape, each of which has a bitmap associated with it. When dragging starts, a new wxDragImage is created using the shape's bitmap, and BeginDrag is called. When mouse motion is detected, wxDragImage::Move is called to render the shape at the appropriate position on the window. Finally, when the left mouse button is released, the drag image is destroyed, and the dragged shape is redrawn in its new position.

 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());         }     } } 

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.

When you start a drag, just before calling wxDragImage::Show, you normally need to first erase the object you're about to drag so that wxDragImage can maintain a backing bitmap consisting of the window without the dragged object, and so that the object can be superimposed onto this snapshot as it is dragged. This will cause a slight flicker at the start of the drag. To eliminate this (for wxGenericDragImage only), override the function UpdateBacking FromWindow and on the memory device context passed to you, draw the window contents minus the object about to be dragged. Now you don't have to erase the object before the drag image is shown, and the next time the image is moved, the correct window contents will be drawn, resulting in a completely smooth drag operation.

    team bbl



    Cross-Platform GUI Programming with wxWidgets
    Cross-Platform GUI Programming with wxWidgets
    ISBN: 0131473816
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net