您可以重新实现 QStandardItemModel 并覆盖 canDropMimeData() 方法。还有其他方法,但如果您已经对QStandardItemModel 感到满意,他们可能会更多地参与其中。实现您自己的模型可能具有性能优势,特别是如果您的数据结构相当简单(如单列列表)。这也可以让您更一般地自定义拖放行为。
请注意,这完全忽略了动作类型(QStandardItemModel 默认只允许移动和复制)。将一个项目移动到另一个项目上将完全删除目标项目 - 这可能不是您想要的,而是一个单独的问题(请参阅下面代码中的 cmets)。
您也可以在dropMimeData() 方法中实现相同的逻辑(在调用基类方法之前),但我不确定我是否看到任何优势。并且通过使用canDropMimeData(),用户还可以获得关于什么是可行的和不可行的视觉反馈。
#include <QStandardItemModel>
class ItemModel : public QStandardItemModel
{
public:
using QStandardItemModel::QStandardItemModel;
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override
{
if (!QStandardItemModel::canDropMimeData(data, action, row, column, parent))
return false;
const int role = Qt::UserRole + 1; // what QStandardItem uses for setData() by default
int originPriority;
int destPriority;
// Find destination item priority.
if (parent.isValid()) {
// dropping onto an item
// Note: if you don't want MoveAction to overwrite items you could:
// if (action == Qt::MoveAction) return false;
destPriority = parent.data(role).toInt();
}
else if (row > -1) {
// dropping between items
destPriority = this->data(index(row, 0), role).toInt();
}
else {
// dropping somewhere else onto the view, treat it as drop after last item in model
destPriority = this->data(index(rowCount() - 1, 0), role).toInt();
}
// Need to find priority of item(s) being dragged (encoded in mime data). Could be several.
// This part decodes the mime data in a way compatible with how QAbstractItemModel encoded it.
// (QStandardItemModel includes it in the mime data alongside its own version)
QByteArray ba = data->data(QAbstractItemModel::mimeTypes().first());
QDataStream ds(&ba, QIODevice::ReadOnly);
while (!ds.atEnd()) {
int r, c;
QMap<int, QVariant> v;
ds >> r >> c >> v;
// If there were multiple columns of data we could also do a
// check on the column number, for example.
originPriority = v.value(role).toInt();
if (originPriority != destPriority)
break; //return false; Could exit here but keep going to print our debug info.
}
qDebug() << "Drop parent:" << parent << "row:" << row <<
"destPriority:" << destPriority << "originPriority:" << originPriority;
if (originPriority != destPriority)
return false;
return true;
}
};
作为参考,这是QAbstractItemModel encodes data 的方法(并在下一个方法中对其进行解码)。
添加:
好的,这有点困扰我,所以这里有一个更高效的版本... :-) 它通过在拖动开始时将拖动项目的优先级直接嵌入到 mime 数据中来节省大量解码时间。
#include <QStandardItemModel>
#define PRIORITY_MIME_TYPE QStringLiteral("application/x-priority-data")
class ItemModel : public QStandardItemModel
{
public:
using QStandardItemModel::QStandardItemModel;
QMimeData *mimeData(const QModelIndexList &indexes) const override
{
QMimeData *mdata = QStandardItemModel::mimeData(indexes);
if (!mdata)
return nullptr;
// Add our own priority data for more efficient evaluation in canDropMimeData()
const int role = Qt::UserRole + 1; // data role for priority value
int priority = -1;
bool ok;
for (const QModelIndex &idx : indexes) {
// Priority of selected item
const int thisPriority = idx.data(role).toInt(&ok);
// When dragging multiple items, check that the priorities of all selected items are the same.
if (!ok || (priority > -1 && thisPriority != priority))
return nullptr; // Cannot drag items with different priorities;
priority = thisPriority;
}
if (priority < 0)
return nullptr; // couldn't find a priority, cancel the drag.
// Encode the priority data
QByteArray ba;
ba.setNum(priority);
mdata->setData(PRIORITY_MIME_TYPE, ba);
return mdata;
}
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override
{
if (!QStandardItemModel::canDropMimeData(data, action, row, column, parent))
return false;
if (!data->hasFormat(PRIORITY_MIME_TYPE))
return false;
const int role = Qt::UserRole + 1; // what QStandardItem uses for setData() by default
int destPriority = -1;
bool ok = false;
// Find destination item priority.
if (parent.isValid()) {
// dropping onto an item
destPriority = parent.data(role).toInt(&ok);
}
else if (row > -1) {
// dropping between items
destPriority = this->data(index(row, 0), role).toInt(&ok);
}
else {
// dropping somewhere else onto the view, treat it as drop after last item in model
destPriority = this->data(index(rowCount() - 1, 0), role).toInt(&ok);
}
if (!ok || destPriority < 0)
return false;
// Get priority of item(s) being dragged which we encoded in mimeData() method.
const int originPriority = data->data(PRIORITY_MIME_TYPE).toInt(&ok);
qDebug() << "Drop parent:" << parent << "row:" << row
<< "destPriority:" << destPriority << "originPriority:" << originPriority;
if (!ok || originPriority != destPriority)
return false;
return true;
}
};