c++ - 上下文依赖于 QListView 中的拖放

标签 c++ qt drag-and-drop qabstractitemmodel qlistview

在我的一个项目中,我必须管理一个项目列表,可以通过使用拖放来重新排列它们的顺序。

现在,所有项目都有优先级,用户无法更改。列表中元素的顺序有一个限制,即优先级低的元素必须在前,但优先级相同的元素可以互换。

例如,下面的列表是合理的:

(A,1),(B,1),(C,1),(D,2),(E,3)

而以下内容已损坏:

(A,1),(B,1),(E,3),(D,2)

以下代码显示了我的问题的起点:

#include <QApplication>
#include <QFrame>
#include <QHBoxLayout>
#include <QListView>
#include <QStandardItemModel>

QStandardItem* create(const QString& text, int priority) {
    auto ret = new QStandardItem(text);
    ret->setData(priority);
    return ret;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    auto frame = new QFrame;
    frame->setLayout(new QVBoxLayout);
    auto view = new QListView;
    frame->layout()->addWidget(view);
    auto model = new QStandardItemModel;
    view->setModel(model);
    model->appendRow(create("1. A", 1));
    model->appendRow(create("1. B", 1));
    model->appendRow(create("2. X", 2));
    model->appendRow(create("2. Y", 2));
    model->appendRow(create("2. Z", 2));

    view->setDragEnabled(true);
    view->viewport()->setAcceptDrops(true);
    view->setDropIndicatorShown(true);
    view->setDragDropMode(QAbstractItemView::DragDropMode::InternalMove);
    view->setDefaultDropAction(Qt::DropAction::MoveAction);
    view->setDragDropOverwriteMode(false);

    frame->show();
    return a.exec();
}

现在,DefaultDropAction 必须根据要移动的项目以及要放置的项目更改上下文。

如果两个元素的优先级相等,那么我就有了一个MoveAction。如果两个元素的优先级不同,我有一个 IgnoreAction

如果不在 QListView 上实现我的,是否可以实现这种行为? 可以通过调整自定义 QAbstractItemModel 来实现。

一个可能的解决方法甚至可能是放弃拖放界面并使用向上和向下箭头键来移动项目。或更一般的具有剪切和粘贴操作的 Action 。但是,我真的更喜欢坚持使用拖放界面。

最佳答案

您可以重新实现 QStandardItemModel 并覆盖 canDropMimeData() 方法。还有其他方法,但如果您已经对 QStandardItemModel 感到满意,它们可能会涉及更多。实现您自己的模型可能具有性能优势,尤其是当您的数据结构相当简单(如单列列表)时。这还可以让您更普遍地自定义拖放行为。

请注意,这会完全忽略操作类型(QStandardItemModel 默认情况下仅允许移动和复制)。将一个项目移动到另一个项目上将完全删除目标项目——这可能不是您想要的,而是一个单独的问题(请参阅下面代码中的注释)。

您也可以在 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;
        }
};

关于c++ - 上下文依赖于 QListView 中的拖放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57769063/

相关文章:

c - Gtk 中的拖放问题

Extjs 4 解释拖放如何实现跨两个网格或树形面板

jQuery 可放置鼠标悬停传播

qt - QML 中的自定义附加属性

c++ - (Qt) 如何使用另一个函数的值?

c++ - 奇怪的 Adob​​e Alchemy 编译器失败

c++ - 将接受花括号初始化列表并推导长度的数组类

c++ - 可变Qt Canvas 的最佳方式

c++ - 标准化 2D/3D vector/坐标类

c++ - 对功能代码块的 undefined reference