•. 概述
•. 使用便利类
•. 启用条目的拖放
•. 对导出的数据进行编码
•. 将放开的数据插入到模型中
•. 对导入的数据进行解码
Qt的拖放设施 被模型/视图框架完整地支持。列表 、表格和树中的条目可以在视图里面拖动,而数据可以作为 MIME编码的数据来导入导出。
标准视图自动支持内部的拖放 ,在那里条目 被到处移动以改变它们被显示的顺序。默认地 ,这些视图的拖放没有 被启用,因为它们 被配置为进行最简单、通用的用途。要允许条目 被到处拖动,视图的一些特定属性需要 被启用,而且条目自己也必须允许拖动的发生 。
一个只允许条目从一个视图里面导出 、而不允许数据被扔到视图里去的模型的要求,比那些完全启用拖放的模型的要求要少。
查看模型子类化参考以获得更多关于在新模型中启用拖放支持的信息 。
默认地 , 在 QListWidget , QTableWidget 和 QTreeWidget 中使用的每种视图都被配置成使用不同的标志的集合。例如 ,每个 QListWidgetItem 或者 QTreeWidgetItem在 初始状态是启用的、可勾选的、可选中的,而且可以用来作为一个拖放操作的源头;每个 QTableWidgetItem 还可以 被编辑,而且可以用来作为一个拖放操作的目标。
尽管所有的标准条目 都有一个或者两个关于拖放的标志被设置,你通常需要在视图中设置各种各样的属性以使用内置的对拖放的支持 :
•. 要启用条目的拖动 ,设置视图的 dragEnabled 属性为真。
•. 要允许用户将内部或外部条目扔进视图里 ,设置视图的 viewport() 的 acceptDrops 属性为真。
•. 要给用户显示出假如当前 被拖放的 条目 被扔进来的话会放置在哪里,就设置视图的 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::ItemIsDragEnabled 和 Qt::ItemIsDropEnabled 值来为它的每个条目启用拖放:
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);
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;
}
由于一个模型索引的列表 被提供给这个函数,这种实现方式是足够通用的,可以用于层次型和非层次型模型。
注意自定义的数据类型必须 被声明为元对象,而且必须为它们实现流操作符。查看 QMetaObject 类的描述以获得一些细节 。
任意给定的模型处理放下的数据的方式取决于它的类型(列表 、表格或者树)以及它的内容倾向于显示给用户的方式。一般地 ,用来调整放下的数据的方法应当是最适合模型的底层数据存储的那一种。
不同类型的模型倾向于以不同的方式处理放下的数据 。列表和表格模型只提供一个用来储存数据的平面结构 。作为结果 ,当数据被扔到视图中一个已有的条目上的时候,它们可能插入新的行(或者列),或者可能使用提供的一些数据覆盖模型中的条目的内容。树形模型通常可以将包含新数据的子条目添加到它们的底层数据存储中 ,因此在用户考虑的情况下会表现得更有预见性 。
放入的数据是由一个模型的 QAbstractItemModel::dropMimeData ()的重载处理的 。例如 ,一个处理简单字符串列表的模型可以提供一个对放到已有的条目上的数据和对放到模型的顶级层次 (也就是说,放到一个无效的条目上) 的数据进行不同的处理的实现 。
模型首先需要确认操作应当 被执行,提供的数据是一个可用的格式,并且它的目的地在模型中是有效的:
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;
如果提供的数据不是纯文本的 ,或者给出的放置目的地的列号是无效的,那么一个简单的一列的字符串列表模型可以提示失败。
取决于数据是否 被扔到一个已存在的条目上,即将被插入模型的数据被不同地对待。在这个简单的示例中 ,我们想要允许数据 被扔到 已有的条目之间 、列表的第一个条目之前,以及最后一个条目之后。
当一个放置操作发生时 ,对应于父条目的模型索引或者是有效的 ,表明放置操作发生在一个条目上,或者是无效的,表明放置操作发生在视图中对应于模型的顶级层次的某个地方。
int beginRow;
if (row != -1)
beginRow = row;
我们一开始检查提供的行号 ,看看我们是否可以用它来插入条目到模型中,忽略关于亲代索引是否有效的问题 。
else if (parent.isValid())
beginRow = parent.row();
如果亲代索引是有效的 ,那么放置操作就发生在一个条目上 。在这个简单的列表模型中 ,我们找出条目的行号,并且使用那个值来将放置的条目插入到模型的顶级层次中。
else
beginRow = rowCount(QModelIndex());
当放置操作发生在视图中别的地方 ,并且行号不可用的时候,我们将条目附加到模型的顶级层次中。
在层次 型模型中,当一个放置操作发生在一个条目上时,最好是将新条目作为那个条目的子代条目插入到模型中 。在这里展示的简单示例中 ,模型只有一个级别 ,所以这种实现方式是不合适的。
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());
foreach (QString text, newItems) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
}
注意 ,模型一般需要提供 QAbstractItemModel::insertRows ()和 QAbstractItemModel::setData ()函数的实现。
参考条目视图解 谜示例。
Your opinionsHxLauncher: Launch Android applications by voice commands