Home · 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.

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

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

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.

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, when a drop occurs between existing top-level items in the model, the specified parent index is invalid and the row corresponds to the row in the model in which to insert new items:

        int beginRow;

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

When a drop occurs on a top-level item, the specified parent index is valid and new items are inserted into the top level of the model before that item.

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 © 2006 Trolltech Trademarks
Qt 4.1.3