使用项目视图的拖放

模型/视图框架完全支持 Qt 的拖放基础结构。列表、表和树中的项可以拖动到视图中,数据可以作为 MIME 编码的数据导入和导出。

标准视图自动支持内部拖放,其中项目会移动以更改其显示顺序。默认情况下,不会为这些视图启用拖放,因为它们配置为最简单、最常见的用途。若要允许拖动项,需要启用视图的某些属性,并且项本身还必须允许拖动发生。

对于只允许从视图导出项,而不允许将数据放入其中的模型,其需求比完全启用拖放模型的需求要少。

使用方便的视图

在默认情况下,QListWidget、QTableWidget和QTreeWidget所使用的每种类型的项目都配置为使用不同的标志集。例如,每个QListWidgetItem或QTreeWidgetItem最初是启用的、可检查的、可选择的,并且可以用作拖放操作的源;还可以编辑每个QTableWidgetItem,并将其用作拖放操作的目标。

尽管所有的标准项目都设置了一个或两个拖放标志,但你通常需要在视图中设置各种属性,以利用内置的拖放支持:

  1. 要启用项目拖拽,将视图的dragEnabled属性设置为.true
  2. 为了允许用户在视图中删除内部或外部项目,将视图的viewport()的acceptDrops属性设置为.true
  3. 为了向用户显示当前被拖放的项目将被放置在哪里,设置视图的showDropIndicator属性。这为用户提供了关于视图中条目放置位置的持续更新信息。

例如,我们可以使用以下代码在列表小部件中启用拖放功能:

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

其结果是一个列表小部件,它允许项目在视图中到处复制,甚至允许用户在包含相同类型数据的视图之间拖动项目。在这两种情况下,项目都是复制而不是移动的。

为了让用户能够在视图中移动条目,我们必须设置列表小部件的dragDropMode:

	listWidget->setDragDropMode(QAbstractItemView::InternalMove);

使用模型/视图类

设置拖放视图遵循与便利视图相同的模式。例如,QListView可以像QListWidget一样被设置:

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

由于对视图显示的数据的访问是由模型控制的,所使用的模型还必须提供对拖放操作的支持。模型支持的操作可以通过重新实现QAbstractItemModel::supportedDropActions()函数来指定。例如,使用以下代码启用复制和移动操作:

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

可以给出任何来自Qt::DropActions的值的组合,但需要编写模型来支持它们。例如,要让Qt::MoveAction在列表模型中正确使用,模型必须提供QAbstractItemModel::removeRows()的实现,可以直接实现,也可以从它的基类继承实现。

为项目启用拖放

模型通过重新实现QAbstractItemModel::flags()函数来提供合适的标志,向视图指示哪些项可以被拖放,哪些将接受拖放。
例如,一个模型提供了一个基于QAbstractListModel的简单列表,它可以通过确保返回的标志包含Qt:: itemdragenabled和Qt:: itemdropenabled值来启用每个项目的拖放:

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

请注意,可以将项目拖放到模型的顶层,但是只对有效的项目启用拖放。
在上面的代码中,因为模型是从QStringListModel派生出来的,所以我们通过调用其flags()函数的实现来获得一组默认的标志。

对导出的数据进行编码

当数据项通过拖放操作从模型中导出时,它们被编码成与一个或多个MIME类型对应的适当格式。模型通过重新实现QAbstractItemModel::mimeTypes()函数来声明它们可以用来提供项目的MIME类型,返回一个标准MIME类型列表。

例如,一个只提供纯文本的模型将提供以下实现:

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

模型还必须提供代码,以便以所宣传的格式对数据进行编码。这是通过重新实现QAbstractItemModel::mimeData()函数来提供QMimeData对象来实现的,就像在任何其他拖放操作中一样。
下面的代码显示了如何将对应于给定索引列表的每个数据项编码为纯文本,并存储在QMimeData对象中。

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

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

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

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

由于向函数提供了一组模型索引,所以这种方法一般足以用于分层模型和非分层模型。
注意,自定义数据类型必须声明为元对象,并且必须为它们实现流操作符。有关详细信息,请参阅QMetaObject类描述。

将删除的数据插入到模型中

任何给定模型处理被删除数据的方式取决于它的类型(列表、表或树)和它的内容可能呈现给用户的方式。通常,用于容纳删除数据的方法应该是最适合模型的底层数据存储的方法。

不同类型的模型往往以不同的方式处理丢失的数据。列表和表模型只提供存储数据项的平面结构。因此,当数据被拖放到视图中的现有项上时,它们可能会插入新的行(和列),或者它们可能会使用提供的某些数据覆盖模型中的项内容。树模型通常能够将包含新数据的子项添加到其基础数据存储中,因此,就用户而言,树模型的行为更加可预测。

被删除的数据由模型的QAbstractItemModel::dropMimeData()的重新实现来处理。例如,一个处理简单字符串列表的模型可以提供一个实现,它可以分别处理现有项上的数据和模型顶层(即无效项上的数据)上的数据。
通过重新实现QAbstractItemModel::canDropMimeData(),模型可以禁止丢弃某些项,或者依赖于被丢弃的数据。
模型首先必须确保操作应该被执行,提供的数据格式可以使用,并且它在模型中的目标是有效的。

bool DragDropListModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(parent);

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

    if (column > 0)
        return false;

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

    if (action == Qt::IgnoreAction)
        return true;

如果提供的数据不是纯文本,或者为删除指定的列号无效,那么简单的单列字符串列表模型可以指示失败。
要插入到模型中的数据根据是否被拖放到现有项上而得到不同的处理。在这个简单的示例中,我们希望允许在现有项之间、在列表的第一个项之前和最后一个项之后进行删除操作。
当删除发生时,与父项目对应的模型索引要么是有效的,表明删除发生在一个项目上,要么是无效的,表明删除发生在与模型顶层对应的视图中的某个地方。

   int beginRow;

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

我们首先检查提供的行号,看看是否可以使用它将项插入到模型中,而不管父索引是否有效。

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

如果父模型索引有效,则对项进行删除。在这个简单的列表模型中,我们找到项目的行号,并使用该值将被拖放的项目插入到模型的顶层。

else
        beginRow = rowCount(QModelIndex());

当删除在视图的其他地方发生,并且行号不可用时,我们将项目添加到模型的顶层。
在层次模型中,当一个项目上发生drop时,最好将新项目作为该项目的子项目插入到模型中。在这里展示的简单示例中,模型只有一个级别,因此这种方法是不合适的。

导入数据的解码

dropMimeData()的每个实现也必须解码数据并将其插入到模型的底层数据结构中。
对于一个简单的字符串列表模型,编码的项可以被解码并流到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;
    }

然后可以将字符串插入到基础数据存储中。为了保持一致性,这可以通过模型自己的接口实现:

 insertRows(beginRow, rows, QModelIndex());
    for (const QString &text : qAsConst(newItems)) {
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, text);
        beginRow++;
    }

    return true;
}

注意,模型通常需要提供QAbstractItemModel::insertRows()和QAbstractItemModel::setData()函数的实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值