Qt QAbstractItemModel 重新排列图像
Posted
技术标签:
【中文标题】Qt QAbstractItemModel 重新排列图像【英文标题】:Qt AbstractItemModel rearranging images 【发布时间】:2015-10-16 15:09:41 【问题描述】:我想在 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;
【问题讨论】:
【参考方案1】:QDataStream
不会将您的图像数据编码为 png。例如,为什么它不将其编码为 bmp 或 gif?您将编码为 application/x-qabstractitemmodeldatalist
的默认 mimetype - 这是因为您没有覆盖 mimeTypes()
。
由于您可能希望支持一次移动多个项目,因此您应该坚持使用x-qabstractitemmodeldatalist
,并对其进行适当的编码/解码。详情请见this question。
请注意,对于 hasImage
,此 mimetype不会返回 true,因为数据是角色值映射列表。
其他问题:
您将纹理泄漏到各处。您应该使用std::shared_ptr
或QSharedPointer
而不是原始指针。
insertrows
正在将同一个纹理实例放入多个条目中。如果您尝试删除纹理,除非使用共享指针,否则您将无法避免多次删除。
dropMimeData
必须对row == -1
做出反应:这表明掉落直接发生在parent
指示的项目上。
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();
【讨论】:
首先,感谢您的回复。你的例子有效。但是,某些行为不是我所需要的。我该如何做到这一点,当我将图像拖到另一个图像上时,它会将其插入目标之前而不是替换目标? (理想情况下,当您悬停以进行预览时,我想将图像推过去,但不确定是否可行)另外,当我将图像拖到空白空间时,我希望将其移动到最后,但我似乎无法把它拖到那里。 @user3554386 对我来说,在 Qt 5.4 下,它会插入,而不是替换。无论如何,这还不完整,您需要为Qt::DropAction
添加处理。目前它正在复制图像,而不是移动它们。如果您只想支持移动,则需要将其设置在视图上。
我有 ser setDragDropMode(QListView::InternalMove);
和 setDefaultDropAction(Qt::MoveAction);
但没有效果。我也在使用最新的 Qt以上是关于Qt QAbstractItemModel 重新排列图像的主要内容,如果未能解决你的问题,请参考以下文章
以编程方式检查QAbstractItemModel / QTreeView中的项目
Qt入门教程数据模型篇QAbstractItemModel抽象模型基类
Qt入门教程数据模型篇QAbstractItemModel抽象模型基类
如何在 QAbstractItemModel 中为 QTreeView 创建人工节点