Qt: Как реализовать простое внутреннее перетаскивание для изменения порядка элементов в QListView с использованием пользовательской модели - PullRequest
0 голосов
/ 29 июня 2019

У меня есть QList пользовательских структур, и я использую пользовательский класс модели (подкласс QAbstractListModel) для отображения этих структур в 1-мерном QListView.Я переопределил методы rowCount, flags и data для создания отображаемой строки из элементов структуры.

Теперь я хотел бы включить внутреннее перетаскивание, чтобы иметь возможность переупорядочивать элементы в спискеперетаскивая их между несколькими другими предметами, но эта задача кажется невероятно сложной.Что именно мне нужно переопределить и какие параметры мне нужно установить?Я пробовал много вещей, я пытался

view->setDragEnabled( true );
view->setAcceptDrops( true );
view->setDragDropMode( QAbstractItemView::InternalMove );
view->setDefaultDropAction( Qt::MoveAction );

Я пытался

Qt::DropActions supportedDropActions() const override {
    return Qt::MoveAction;
}
Qt::ItemFlags flags( const QModelIndex & index ) const override{
    return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled;
}

Я пытался реализовать insertRows и removeRows, но это все еще не работает.

Я не нашел ни одного примера кода, делающего именно это.Официальная документация очень глубоко посвящена тому, как работает шаблон представления / модели и как перетаскивать элементы из внешних приложений или из других виджетов, но я не хочу ничего из этого.Я хочу использовать только простое внутреннее перетаскивание для ручного изменения порядка элементов в одном представлении списка.

Может кто-нибудь помочь мне?Или я схожу с ума от этого.

РЕДАКТИРОВАТЬ: добавление реализации insertRows / removeRows по запросу:

bool insertRows( int row, int count, const QModelIndex & parent ) override
{
    QAbstractListModel::beginInsertRows( parent, row, row + count - 1 );

    for (int i = 0; i < count; i++)
        AObjectListModel<Object>::objectList.insert( row, Object() );

    QAbstractListModel::endInsertRows();
    return true;
}

bool removeRows( int row, int count, const QModelIndex & parent ) override
{
    if (row < 0 || row + count > AObjectListModel<Object>::objectList.size())
        return false;

    QAbstractListModel::beginRemoveRows( parent, row, row + count - 1 );

    for (int i = 0; i < count; i++)
        AObjectListModel<Object>::objectList.removeAt( row );

    QAbstractListModel::endRemoveRows();
    return true;
}

objectList - это QList, где Object - это параметр шаблона.

Ответы [ 2 ]

3 голосов
/ 30 июня 2019

Когда вы хотите реорганизовать элементы в пользовательской модели, вы должны выполнить все необходимые действия: - как вставить и удалить строку - как получить и установить данные - как сериализовать элементы (построить mimedata) - какunserialize items

Пример с пользовательской моделью с QStringList в качестве источника данных:

Минимальная реализация модели должна быть:

class CustomModel: public QAbstractListModel
{
public:
    CustomModel()
    {
        internalData = QString("abcdefghij").split("");
    }
    int rowCount(const QModelIndex &parent) const
    {
        return internalData.length();
    }
    QVariant data(const QModelIndex &index, int role) const
    {
        if (!index.isValid() || index.parent().isValid())
            return QVariant();
        if (role != Qt::DisplayRole)
            return QVariant();
        return internalData.at(index.row());
    }
private:
    QStringList internalData;   
};

Мы должныдобавьте способ вставки / удаления строк и задайте данные:

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole)
    {
        if (role != Qt::DisplayRole)
            return false;
        internalData[index.row()] = value.toString();
        return true;
    }
    bool insertRows(int row, int count, const QModelIndex &parent)
    {
        if (parent.isValid())
            return false;
        for (int i = 0; i != count; ++i)
            internalData.insert(row + i, "");
        return true;
    }
    bool removeRows(int row, int count, const QModelIndex &parent)
    {
        if (parent.isValid())
            return false;
        beginRemoveRows(parent, row, row + count - 1);
        for (int i = 0; i != count; ++i)
            internalData.removeAt(row);
        endRemoveRows();
        return true;
    }

Для части перетаскивания:

Сначала нам нужно определить тип MIME, чтобы определить способ, которым мы будемдесериализовать данные:

    QStringList mimeTypes() const
    {
        QStringList types;
        types << CustomModel::MimeType;
        return types;
    }

Где CustomModel::MimeType - постоянная строка, такая как "application/my.custom.model"

. Метод canDropMimeData будет использоваться для проверки того, являются ли отброшенные данные допустимыми или нет.Итак, мы можем отбросить внешние данные:

    bool canDropMimeData(const QMimeData *data,
        Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex& /*parent*/)
    {
        if ( action != Qt::MoveAction || !data->hasFormat(CustomModel::MimeType))
            return false;
        return true;
    }

Затем мы можем создать наши данные MIME на основе внутренних данных:

    QMimeData* 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(CustomModel::MimeType, encodedData);
        return mimeData;
    }

Теперь нам нужно обработать пропущенные данные.Мы должны десериализовать данные MIME, вставить новую строку, чтобы установить данные в нужном месте (для Qt::MoveAction старая строка будет автоматически удалена. Вот почему нам пришлось реализовать removeRows):

bool 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;
        else if (action  != Qt::MoveAction)
            return false;

        QByteArray encodedData = data->data("application/my.custom.model");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;
        int rows = 0;

        while (!stream.atEnd()) {
            QString text;
            stream >> text;
            newItems << text;
            ++rows;
        }

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

        return true;
    }

Если вам нужна дополнительная информация о системе перетаскивания в Qt, посмотрите документацию .

2 голосов
/ 30 июня 2019

В дополнение к великолепному ответу Ромхи, я хотел бы добавить еще несколько подробностей о том, как это работает и что смущает его.

Официальная документация гласит: QAbstractItemModel имеетреализации по умолчанию mimeTypes, mimeData и dropMimeData, которые должны работать для внутренних операций перемещения и копирования, если правильно реализовать data, setData, insertRows и removeRows.

И с определенной точки зрения они были правы.Он работает без переопределения mimeData и dropMimeData, но только в том случае, если ваша базовая структура данных содержит только одиночные строки, те, которые возвращаются из data и принимаются в setData как DisplayRole.Когда у вас есть список составных объектов (как у меня) с несколькими элементами, только один из которых используется для DisplayRole, например

struct Elem {
    QString name;
    int i;
    bool b;
}

QVariant data( const QModelIndex & index, int role ) const override
{
    return objectList[ index.row() ].name;
}
bool setData( const QModelIndex & index, const QVariant & value, int role ) override
{
    objectList[ index.row() ].name = value.toString();
}

, тогда реализации по умолчанию на самом деле будут делать это

QVariant data = data( oldIndex, Qt::DisplayRole );
insertRows( newIndex, 1 )
setData( newIndex, data, Qt::DisplayRole )
removeRows( oldIndex, 1 )

и, следовательно, только правильно перемещают имена и оставляют остальную часть структуры как есть.Это имеет смысл сейчас, но система настолько сложна, что я не осознавал этого раньше.

Поэтому пользовательские mimeData и dropMimeData необходимы для перемещения всего содержимого структур

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...