簡體   English   中英

在QListView中取決於上下文的拖放

[英]Context depending drag and drop in 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上不實現my的情況下實現此行為,並且可以通過改編自定義的QAbstractItemModel來實現?

可能的解決方法甚至是放棄拖放界面,並使用向上和向下箭頭鍵在周圍移動項目。 或什至更一般的剪切和粘貼操作。 但是,我確實更喜歡使用拖放界面。

您可以重新實現QStandardItemModel並重寫canDropMimeData()方法。 還有其他方法,但是如果您已經對QStandardItemModel滿意的話,它們可能會更多地參與其中。 實現自己的模型可能會帶來性能優勢,尤其是在您的數據結構非常簡單的情況下(例如單列列表)。 通常,這還使您可以更自定義拖放行為。

請注意,這將完全忽略操作類型(默認情況下, QStandardItemModel僅允許移動和復制)。 將一個項目移至另一個項目將完全刪除目標項目-這可能不是您想要的,但是一個單獨的問題(請參見下面代碼中的注釋)。

您也可以在dropMimeData()方法中實現相同的邏輯(在調用基類方法之前),但是我不確定我是否看到任何優點。 而且,通過使用canDropMimeData() ,用戶還可以獲得有關什么是canDropMimeData()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如何編碼數據 (並在下一個方法中對其進行解碼)。

添加 :好的,這讓我有些煩惱,所以這是一個更有效的版本... :-)通過在拖動開始時將拖動項的優先級直接嵌入到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;
        }
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM