Qt AbstractItemModel переупорядочивает изображения

Я хочу переставить набор изображений в qlistview. Я посмотрел на примеры и просто не могу заставить это работать. Когда я перетаскиваю изображение поверх другого изображения, выполняется dropomimedata (), однако его «data-> hasImage ()» всегда ложно. Когда я бросаю изображение в пустое пространство, по какой-то причине dropmimedata () вообще не запускается.

Моя модель должна выглядеть так:

введите описание изображения здесь

Однако после перетаскивания в пустые места это выглядит так:

введите описание изображения здесь

И когда я перетаскиваю изображение поверх другого, ничего не меняется, потому что hasImage всегда ложно. Что я делаю неправильно? Что мне не хватает?

#include "spritemodel.h"

#include <QDebug>
#include <QMimeData>

SpriteModel::SpriteModel() : QAbstractListModel()
{
}

void SpriteModel::setContents(QList<QPair<QImage, QOpenGLTexture*>> &newList)
{
    beginInsertRows(QModelIndex(), 0, newList.size());
    imageList = newList;
    endInsertRows();
}

int SpriteModel::rowCount(const QModelIndex & parent) const
{
    Q_UNUSED(parent);
    return imageList.size();
}

QVariant SpriteModel::data(const QModelIndex & index, int role) const
{
    if (role == Qt::DecorationRole)
        return imageList[index.row()].first;
    else if (role == Qt::DisplayRole)
        return "";
    else
        return QVariant();
}

Qt::DropActions SpriteModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

Qt::ItemFlags SpriteModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);

    if (index.isValid())
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    else
        return Qt::ItemIsDropEnabled | defaultFlags;
}

bool SpriteModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        imageList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

bool SpriteModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    QImage img(imageList[0].first.width(), imageList[0].first.height(), imageList[0].first.format());
    QOpenGLTexture *texture = new QOpenGLTexture(img);

    for (int row = 0; row < rows; ++row) {
        imageList.insert(position, qMakePair(img, texture));
    }

    endInsertRows();
    return true;
}

bool SpriteModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    QImage img = value.value<QImage>();
    QOpenGLTexture *texture = new QOpenGLTexture(img);

    if (index.isValid() && role == Qt::EditRole) {

        imageList.replace(index.row(), qMakePair(img, texture));
        emit dataChanged(index, index);
        return true;
    }
    return false;
}

QMimeData *SpriteModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    foreach (const QModelIndex &index, indexes) {
        if (index.isValid()) {
            QVariant img = data(index, Qt::DecorationRole);
            stream << img;
        }
    }

    mimeData->setData("image/png", encodedData);
    return mimeData;
}

bool SpriteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    if (data->hasImage()) {
        qDebug() << "wut";
        QImage img = qvariant_cast<QImage>(data->imageData());
        QOpenGLTexture *texture = new QOpenGLTexture(img);

        beginInsertRows(parent, 0, 1); // test
        imageList.insert(row, qMakePair(img, texture));
        endInsertRows();

        emit dataChanged(QModelIndex(),QModelIndex());
        return true;
    }
    return false;
}

person Fundies    schedule 26.07.2015    source источник


Ответы (1)


QDataStream не кодирует данные вашего изображения как png. Почему бы ему, например, не закодировать его как BMP или GIF? Тип mime по умолчанию, который вы будете кодировать как application/x-qabstractitemmodeldatalist - это потому, что вы не переопределяли mimeTypes().

Поскольку вы, вероятно, хотите поддерживать одновременное перемещение нескольких элементов, вам следует придерживаться x-qabstractitemmodeldatalist и соответствующим образом кодировать / декодировать его. Дополнительные сведения см. В этом вопросе.

Обратите внимание, что этот mimetype не возвращает значение true для hasImage, поскольку данные представляют собой список сопоставлений значений ролей.

Прочие проблемы:

  1. Вы просачиваете текстуры повсюду. Вы должны использовать std::shared_ptr или QSharedPointer вместо необработанного указателя.

  2. insertrows помещает один и тот же экземпляр текстуры в несколько записей. Если вы когда-либо попытаетесь удалить текстуры, вы не сможете избежать множественных удалений, если не используете общий указатель.

  3. dropMimeData должен реагировать на row == -1: это означает, что падение произошло непосредственно над элементом, обозначенным parent.

  4. setContents не добавляют никаких элементов, они полностью сбрасывают модель.

Пример ниже иллюстрирует все моменты.

#include <QtWidgets>

const auto mimeType = QStringLiteral("application/x-qabstractitemmodeldatalist");

class SpriteModel : public QAbstractListModel {
public:
   typedef QSharedPointer<QOpenGLTexture> TexturePtr;
   typedef QPair<QImage, TexturePtr> Item;
private:
   QList<Item> m_imageList;
public:
   SpriteModel(QObject * parent = 0) : QAbstractListModel(parent) {}

   static Item makeItem(const QImage & image) {
      return qMakePair(image, TexturePtr(new QOpenGLTexture(image)));
   }

   void setContents(QList<Item> &newList) {
      beginResetModel();
      m_imageList = newList;
      endResetModel();
   }

   int rowCount(const QModelIndex &) const Q_DECL_OVERRIDE {
      return m_imageList.size();
   }

   QVariant data(const QModelIndex & index, int role) const Q_DECL_OVERRIDE {
      if (role == Qt::DecorationRole)
         return m_imageList[index.row()].first;
      else if (role == Qt::DisplayRole)
         return "";
      else
         return QVariant();
   }

   Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE {
      return Qt::CopyAction | Qt::MoveAction;
   }

   Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE {
      auto defaultFlags = QAbstractListModel::flags(index);
      if (index.isValid())
         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
      else
         return Qt::ItemIsDropEnabled | defaultFlags;
   }

   bool removeRows(int position, int rows, const QModelIndex &) Q_DECL_OVERRIDE {
      beginRemoveRows(QModelIndex(), position, position+rows-1);
      for (int row = 0; row < rows; ++row) {
         m_imageList.removeAt(position);
      }
      endRemoveRows();
      return true;
   }

   bool insertRows(int position, int rows, const QModelIndex &) Q_DECL_OVERRIDE {
      beginInsertRows(QModelIndex(), position, position+rows-1);
      auto size = m_imageList.isEmpty() ? QSize(10, 10) : m_imageList.at(0).first.size();
      QImage img(size, m_imageList[0].first.format());
      for (int row = 0; row < rows; ++row) {
         m_imageList.insert(position, makeItem(img));
      }
      endInsertRows();
      return true;
   }

   bool setData(const QModelIndex &index, const QVariant &value, int role) Q_DECL_OVERRIDE {
      if (index.isValid() && role == Qt::EditRole) {
         m_imageList.replace(index.row(), makeItem(value.value<QImage>()));
         emit dataChanged(index, index);
         return true;
      }
      return false;
   }

   QMimeData *mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE {
      auto mimeData = new QMimeData();
      QByteArray encodedData;
      QDataStream stream(&encodedData, QIODevice::WriteOnly);

      qDebug() << "mimeData" << indexes;

      for (const auto & index : indexes) {
         if (! index.isValid()) continue;
         QMap<int, QVariant> roleDataMap;
         roleDataMap[Qt::DecorationRole] = data(index, Qt::DecorationRole);
         stream << index.row() << index.column() << roleDataMap;
      }
      mimeData->setData(mimeType, encodedData);
      return mimeData;
   }

   bool canDropMimeData(const QMimeData *data,
                        Qt::DropAction, int, int column, const QModelIndex & parent) const Q_DECL_OVERRIDE
   {
      return data->hasFormat(mimeType) && (column == 0 || (column == -1 && parent.column() == 0));
   }

   bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) Q_DECL_OVERRIDE {
      Q_UNUSED(column);
      qDebug() << "drop" << action << row << column << parent;
      if (! data->hasFormat(mimeType)) return false;

      auto encoded = data->data(mimeType);
      QDataStream stream(&encoded, QIODevice::ReadOnly);
      QList<QImage> images;
      while (! stream.atEnd()) {
         int row, col;
         QMap<int, QVariant> roleDataMap;
         stream >> row >> col >> roleDataMap;
         auto it = roleDataMap.find(Qt::DecorationRole);
         if (it != roleDataMap.end()) {
            images << it.value().value<QImage>();
         }
      }
      if (row == -1) row = parent.row();
      if (! images.isEmpty()) {
         beginInsertRows(parent, row, row+images.size() - 1);
         qDebug() << "inserting" << images.count();
         for (auto & image : images)
            m_imageList.insert(row ++, makeItem(image));
         endInsertRows();
         return true;
      }
      return false;
   }
};

QImage makeImage(int n) {
   QImage img(64, 128, QImage::Format_RGBA8888);
   img.fill(Qt::transparent);
   QPainter p(&img);
   p.setFont(QFont("Helvetica", 32));
   p.drawText(img.rect(), Qt::AlignCenter, QString::number(n));
   return img;
}

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   QList<SpriteModel::Item> items;
   for (int i = 0; i < 5; ++i) items << SpriteModel::makeItem(makeImage(i));
   SpriteModel model;
   model.setContents(items);
   QListView view;
   view.setModel(&model);
   view.setViewMode(QListView::IconMode);
   view.setSelectionMode(QAbstractItemView::ExtendedSelection);
   view.setDragEnabled(true);
   view.setAcceptDrops(true);
   view.setDropIndicatorShown(true);
   view.show();
   qDebug() << model.mimeTypes();
   return a.exec();
}
person Kuba hasn't forgotten Monica    schedule 27.07.2015
comment
Во-первых, спасибо за ответ. Ваш пример работает. Однако некоторые особенности поведения мне не нужны. Как мне сделать так, чтобы когда я перетаскивал изображение поверх другого, оно вставлялось перед целью вместо того, чтобы заменять цель? (В идеале я хотел бы подтолкнуть изображения, когда вы наводите курсор для предварительного просмотра, но не уверен, возможно ли это) Кроме того, когда я перетаскиваю изображение в пустое пространство, я хочу, чтобы оно переместило его в конец, но я не могу показаться перетащить туда. - person Fundies; 28.07.2015
comment
@ user3554386 Для меня в Qt 5.4 он выполняет вставку, а не замену. Во всяком случае, это еще не все, нужно добавить обработку для Qt::DropAction. В настоящее время он копирует изображения, а не перемещает их. Если вы хотите только поддерживать ходы, вам нужно установить это в представлении. - person Kuba hasn't forgotten Monica; 28.07.2015
comment
У меня есть ser setDragDropMode(QListView::InternalMove); и setDefaultDropAction(Qt::MoveAction);, но это не повлияло. Также я использую последнюю версию Qt - person Fundies; 28.07.2015