Qt logo


Drag and Drop with Qt


Drag-and-drop is a direct-manipulation model for allowing the user to transfer information between and within applications. It is similar in function to the cut-and-paste clipboard model.

Dragging

To start a drag, for example in a mouse motion event, create an object of the QDragObject subclass appropriate for your media, such as QTextDrag for text and QImageDrag for images. Then call the drag() method. This is all you need for simple dragging of existing types.

For example, to start dragging some text from a widget:

  void MyWidget::startDragging()
  {
    QDragObject *d = new QTextDrag( myHighlightedText(), this );
    d->dragCopy();
    // do NOT delete d.
  }

Note that the QDragObject is not deleted after the drag. The QDragObject needs to persist after the drag is apparently finished - it may still be communicating with another process. Eventually Qt will delete the object. If the widget owning the drag object is deleted before then, any pending drop will be cancelled and the drag object deleted. For this reason, you should be careful what the object references.

Dropping

To be able to receive media dropped on a widget, call setAcceptDrops(TRUE) for the widget (eg. in its constructor), and override the event handler methods dragEnterEvent(), dragMoveEvent(), dragLeaveEvent(), and dropEvent().

For example, to accept text and image drops:

  MyWidget::MyWidget(...) :
    QWidget(...)
  {
    ...
    setAcceptDrops(TRUE);
  }

  void MyWidget::dragEnterEvent(QDragEnterEvent* event)
  {
    event->accept(
        QTextDrag::canDecode(event) ||
        QImageDrag::canDecode(event)
    );
  }

  void MyWidget::dropEvent(QDropEvent* event)
  {
    QImage image;
    QString text;

    if ( QImageDrag::decode(event, image) ) {
      insertImageAt(image, event->pos());
    } else if ( QTextDrag::decode(event, text) ) {
      insertTextAt(text, event->pos());
    }
  }

The Clipboard

The QDragObject, QDragEnterEvent, QDragMoveEvent, and QDropEvent classes are all subclasses of QMimeSource - the class of objects which provide typed information. If you base your data transfers on QDragObject, you not only get drag-and-drop, but you also get traditional cut-and-paste for free - the QClipboard has two functions:

 setData(QMimeSource*)
and
 QMimeSource* data()const
. With these, you can trivially put your drag-and-drop oriented information on the clipboard:
  void MyWidget::copy()
  {
    QApplication::clipboard()->setData(
        new QTextDrag(myHighlightedText())
    );
  }

  void MyWidget::paste()
  {
    QString text;
    if ( QTextDrag::decode(QApplication::clipboard()->data(), text) )
        insertText( text );
  }

You can even use QDragObject subclasses as part of file IO. For example, if your application has a subclass of QDragObject that encodes CAD designs in DXF format, your saving and loading code might be:

  void MyWidget::save()
  {
    QFile out(current_file_name);
    out.open(IO_WriteOnly);
    MyCadDrag tmp(current_design);
    out.writeBlock( tmp->encodedData( "image/x-dxf" );
  }

  void MyWidget::load()
  {
    QFile in(current_file_name);
    in.open(IO_ReayOnly);
    if ( !MyCadDrag::decode(in.readAll(), current_design) ) {
        QMessageBox::warning( this, "Format error",
            tr("The file \"%1\" is not in any supported format")
             .arg(current_file_name)
        );
    }
  }

Note how the QDragObject subclass is called "MyCadDrag", not "MyDxfDrag" - because in the future you might extend it to provide DXF, DWG, SVF, WMF, or even QPicture data to other applications.

Drag-and-drop action

In the simpler cases, the target of a drag-and-drop receives a copy of the data being dragged and the source decides whether to delete the original. This is the "Copy" action in QDropEvent. The target may also choose to understand other actions, specifically the Move and Link actions. If the target understands the Move action, it is responsible for both the copy and delete operations and the source will not attempt to delete the data itself. If the target understands the Link, it stores some kind of reference to the original information, and again the source does not delete the original. The most common use of drag-and-drop actions is when performing a Move within the same widget - see the Advanced Drag-and-Drop section below.

The other major use of drag actions is when using a reference type such as text/uri-list, where the dragged data is actually references to files or objects.

Adding new drag-and-drop types

As suggested in the DXF example above, drag-and-drop is not limited to text and images. Any information can be dragged and dropped. To drag information between applications, the two applications need a way to agree on the type of information they both understand. The way they do it is using MIME types - the drag source provides a list of MIME types that it can produce (ordered from most appropriate to least appropriate), and the drop target chooses which of those it can handle. For example, QTextDrag provides support for the "text/plain" MIME type (ordinary unformatted text), and the Unicode formats "text/utf16" and "text/utf8"; QImageDrag provides for "image/*", where * is any image format that Qt supports; and the QUriDrag subclass provides "text/uri-list", a standard format for transferring a list of filenames (or URLs).

To implement drag-and-drop of some type of information for which there is no available QDragObject subclass, the first and most important step is to look for existing formats that are appropriate - the Internet Assigned Numbers Authority (IANA) provides a hierarchical list of MIME media types at the Information Sciences Institute (ISI). This maximizes inter-operability of your software with other software now and in the future.

To support an additional media type, subclass either QDragObject or QStoredDrag. Subclass QDragObject when you need to provide support for multiple media types. Subclass the simpler QStoredDrag when one type is sufficient.

Subclasses of QDragObject will override the const char* format(int i) const and QByteArray encodedData(const char* mimetype) const members, and provide a set-method to encode the media data and static members canDecode() and decode() to decode incoming data, similar to bool canDecode(QMimeSource*) const and QByteArray decode(QMimeSource*) const of QImageDrag. Of course, you can provide drag-only or drop-only support for a media type by omitting some of these methods.

Subclasses of QStoredDrag provide a set-method to encode the media data and the same static members canDecode() and decode() to decode incoming data.

Advanced Drag-and-Drop

In the clipboard model, the user can cut or copy the source information, then later paste it. Similarly in the drag-and-drop model, the user can drag a copy of the information or they can drag the information itself to a new place (moving it). The drag-and-drop model however has an additional complication for the programmer: the program doesn't know whether the user want to cut or copy until the drop (paste) is done! For dragging between applications, it makes no difference, but for dragging within an application, the application must take a little extra care not to tread on its own feet. For example, to drag text around in a document, the drag start point and the drop event might look like this:

  void MyEditor::startDragging()
  {
    QDragObject *d = new QTextDrag(myHighlightedText(), this);
    if ( d->drag() && d->target() != this )
      cutMyHighlightedText();
  }

  void MyEditor::dropEvent(QDropEvent* event)
  {
    QString text;

    if ( QTextDrag::decode(event, text) ) {
      if ( event->source() == this && event->action() == QDropEvent::Move ) {
        // Careful not to tread on my own feet
        event->acceptAction();
        moveMyHighlightedTextTo(event->pos());
      } else {
        pasteTextAt(text, event->pos());
      }
    }
  }

Some widgets are more specific than just a "yes" or "no" response when data is dragged onto them. For example, a CAD program might only accept drops of text onto text objects in the view. In these cases, the dragMoveEvent() is used and an area is given for which the drag is accepted or ignored:

  void MyWidget::dragMoveEvent(QDragMoveEvent* event)
  {
    if ( QTextDrag::canDecode(event) ) {
      MyCadItem* item = findMyItemAt(event->pos());
      if ( item )
        event->accept();
    }
  }

If the computations to find objects are particularly slow, you might find improved performance if you tell the system an area for which you promise the acceptance persists:

  void MyWidget::dragMoveEvent(QDragMoveEvent* event)
  {
    if ( QTextDrag::canDecode(event) ) {
      MyCadItem* item = findMyItemAt(event->pos());
      if ( item ) {
        QRect r = item->areaRelativeToMeClippedByAnythingInTheWay();
        if ( item->type() == MyTextType )
          event->accept( r );
        else
          event->ignore( r );
      }
    }
  }

The dragMoveEvent() can also be used if you need to give visual feedback as the drag progresses, to start timers to scroll the window, or whatever you need (don't forget to stop scrolling timers in a dragLeaveEvent() though).

For a complete example of drag and drop, examine the dragdrop example program, or the QMultiLineEdit widget source code.

Inter-operating with other applications

On X11, the public XDND protocol is used, while on Windows Qt uses the OLE standard. On X11, XDND uses MIME, so no translation is necessary. The Qt API is the same regardless of the platform. On Windows, MIME-aware applications can communicate by using clipboard format names that are MIME types. Already some Windows applications use MIME naming conventions for their clipboard formats. Internally, Qt has facilities for translating proprietary clipboard formats to and from MIME types. This interface will be made public at some time, but if you need to do such translations now, contact your Qt Technical Support service.


Copyright © 1999 Troll TechTrademarks
Qt version 2.0.2