Home · All Namespaces · All Classes · Main Classes · Grouped Classes · Modules · Functions

[Previous: Item View Convenience Classes] [Contents] [Next: Proxy Models]

Using Drag and Drop with Item Views

Overview

Qt's drag and drop infrastructure is fully supported by the model/view framework. Items in lists, tables, and trees can be dragged within the views, and data can be imported and exported as MIME-encoded data.

The standard views automatically support internal drag and drop, where items are moved around to change the order in which they are displayed. By default, drag and drop is not enabled for these views because they are configured for the simplest, most common uses. To allow items to be dragged around, certain properties of the view need to be enabled, and the items themselves must also allow dragging to occur.

The requirements for a model that only allows items to be exported from a view, and which does not allow data to be dropped into it, are fewer than those for a fully-enabled drag and drop model.

See also the Model Subclassing Reference for more information about enabling drag and drop support in new models.

Using Convenience Views

Each of the types of item used with QListWidget, QTableWidget, and QTreeWidget is configured to use a different set of flags by default. For example, each QListWidgetItem or QTreeWidgetItem is initially enabled, checkable, selectable, and can be used as the source of a drag and drop operation; each QTableWidgetItem can also be edited and used as the target of a drag and drop operation.

Although all of the standard items have one or both flags set for drag and drop, you generally need to set various properties in the view itself to take advantage of the built-in support for drag and drop:

For example, we can enable drag and drop in a list widget with the following lines of code:

 QListWidget *listWidget = new QListWidget(this);
 listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
 listWidget->setDragEnabled(true);
 listWidget->viewport()->setAcceptDrops(true);
 listWidget->setDropIndicatorShown(true);

The result is a list widget which allows the items to be copied around within the view, and even lets the user drag items between views containing the same type of data. In both situations, the items are copied rather than moved.

To enable the user to move the items around within the view, we must set the list widget's dragDropMode:

 listWidget->setDragDropMode(QAbstractItemView::InternalMove);

Using Model/View Classes

Setting up a view for drag and drop follows the same pattern used with the convenience views. For example, a QListView can be set up in the same way as a QListWidget:

 QListView *listView = new QListView(this);
 listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
 listView->setDragEnabled(true);
 listView->setAcceptDrops(true);
 listView->setDropIndicatorShown(true);

Since access to the data displayed by the view is controlled by a model, the model used also has to provide support for drag and drop operations. The actions supported by a model can be specified by reimplementing the QAbstractItemModel::supportedDropActions() function. For example, copy and move operations are enabled with the following code:

 Qt::DropActions DragDropListModel::supportedDropActions() const
 {
     return Qt::CopyAction | Qt::MoveAction;
 }

Although any combination of values from Qt::DropActions can be given, the model needs to be written to support them. For example, to allow Qt::MoveAction to be used properly with a list model, the model must provide an implementation of QAbstractItemModel::removeRows(), either directly or by inheriting the implementation from its base class.

Enabling Drag and Drop for Items

Models indicate to views which items can be dragged, and which will accept drops, by reimplementing the QAbstractItemModel::flags() function to provide suitable flags.

For example, a model which provides a simple list based on QAbstractListModel can enable drag and drop for each of the items by ensuring that the flags returned contain the Qt::ItemIsDragEnabled and Qt::ItemIsDropEnabled values:

 Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
 {
     Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

     if (index.isValid())
         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
     else
         return Qt::ItemIsDropEnabled | defaultFlags;
 }

Note that items can be dropped into the top level of the model, but dragging is only enabled for valid items.

In the above code, since the model is derived from QStringListModel, we obtain a default set of flags by calling its implementation of the flags() function.

Encoding Exported Data

When items of data are exported from a model in a drag and drop operation, they are encoded into an appropriate format corresponding to one or more MIME types. Models declare the MIME types that they can use to supply items by reimplementing the QAbstractItemModel::mimeTypes() function, returning a list of standard MIME types.

For example, a model that only provides plain text would provide the following implementation:

 QStringList DragDropListModel::mimeTypes() const
 {
     QStringList types;
     types << "application/vnd.text.list";
     return types;
 }

The model must also provide code to encode data in the advertised format. This is achieved by reimplementing the QAbstractItemModel::mimeData() function to provide a QMimeData object, just as in any other drag and drop operation.

The following code shows how each item of data, corresponding to a given list of indexes, is encoded as plain text and stored in a QMimeData object.

 QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
 {
     QMimeData *mimeData = new QMimeData();
     QByteArray encodedData;

     QDataStream stream(&encodedData, QIODevice::WriteOnly);

     foreach (QModelIndex index, indexes) {
         if (index.isValid()) {
             QString text = data(index, Qt::DisplayRole).toString();
             stream << text;
         }
     }

     mimeData->setData("application/vnd.text.list", encodedData);
     return mimeData;
 }

Since a list of model indexes is supplied to the function, this approach is general enough to be used in both hierarchical and non-heirarchical models.

Note that custom datatypes must be declared as meta objects and that stream operators must be implemented for them. See the QMetaObject class description for details.

Inserting Dropped Data into a Model

The way that any given model handles dropped data depends on both its type (list, table, or tree) and the way its contents is likely to be presented to the user. Generally, the approach taken to accommodate dropped data should be the one that most suits the model's underlying data store.

Different types of model tend to handle dropped data in different ways. List and table models only provide a flat structure in which items of data are stored. As a result, they may insert new rows (and columns) when data is dropped on an existing item in a view, or they may overwrite the item's contents in the model using some of the data supplied. Tree models are often able to add child items containing new data to their underlying data stores, and will therefore behave more predictably as far as the user is concerned.

Dropped data is handled by a model's reimplementation of QAbstractItemModel::dropMimeData(). For example, a model that handles a simple list of strings can provide an implementation that handles data dropped onto existing items separately to data dropped into the top level of the model (i.e., onto an invalid item).

The model first has to make sure that the operation should be acted on, the data supplied is in a format that can be used, and that its destination within the model is valid:

 bool DragDropListModel::dropMimeData(const QMimeData *data,
     Qt::DropAction action, int row, int column, const QModelIndex &parent)
 {
     if (action == Qt::IgnoreAction)
         return true;

     if (!data->hasFormat("application/vnd.text.list"))
         return false;

     if (column > 0)
         return false;

A simple one column string list model can indicate failure if the data supplied is not plain text, or if the column number given for the drop is invalid.

The data to be inserted into the model is treated differently depending on whether it is dropped onto an existing item or not. In this simple example, we want to allow drops between existing items, before the first item in the list, and after the last item.

When a drop occurs, the model index corresponding to the parent item will either be valid, indicating that the drop occurred on an item, or it will be invalid, indicating that the drop occurred somewhere in the view that corresponds to top level of the model.

     int beginRow;

     if (row != -1)
         beginRow = row;

We initially examine the row number supplied to see if we can use it to insert items into the model, regardless of whether the parent index is valid or not.

     else if (parent.isValid())
         beginRow = parent.row();

If the parent model index is valid, the drop occurred on an item. In this simple list model, we find out the row number of the item and use that value to insert dropped items into the top level of the model.

     else
         beginRow = rowCount(QModelIndex());

When a drop occurs elsewhere in the view, and the row number is unusable, we append items to the top level of the model.

In hierarchical models, when a drop occurs on an item, it would be better to insert new items into the model as children of that item. In the simple example shown here, the model only has one level, so this approach is not appropriate.

Decoding Imported Data

Each implementation of dropMimeData() must also decode the data and insert it into the model's underlying data structure.

For a simple string list model, the encoded items can be decoded and streamed into a QStringList:

     QByteArray encodedData = data->data("application/vnd.text.list");
     QDataStream stream(&encodedData, QIODevice::ReadOnly);
     QStringList newItems;
     int rows = 0;

     while (!stream.atEnd()) {
         QString text;
         stream >> text;
         newItems << text;
         ++rows;
     }

The strings can then be inserted into the underlying data store. For consistency, this can be done through the model's own interface:

     insertRows(beginRow, rows, QModelIndex());
     foreach (QString text, newItems) {
         QModelIndex idx = index(beginRow, 0, QModelIndex());
         setData(idx, text);
         beginRow++;
     }

     return true;
 }

Note that the model will typically need to provide implementations of the QAbstractItemModel::insertRows() and QAbstractItemModel::setData() functions.

See also Item Views Puzzle Example.

[Previous: Item View Convenience Classes] [Contents] [Next: Proxy Models]


Copyright © 2008 Nokia Trademarks
Qt 4.4.3