我已经创建了一个QAbstractItemModel,并且填充了数据。我的QTreeView小部件正确地显示了该模型中的每个数据。
现在,我想将该模型序列化到二进制文件中(稍后当然可以将该二进制文件加载回模型)。这可行吗?
我已经创建了一个QAbstractItemModel,并且填充了数据。我的QTreeView小部件正确地显示了该模型中的每个数据。
现在,我想将该模型序列化到二进制文件中(稍后当然可以将该二进制文件加载回模型)。这可行吗?
某些可用模型可能没有实现insertRows
/insertColumns
,而更喜欢使用自定义方法。
像QStandardItemModel
这样的模型可能具有不同类型的基础项。进行反序列化时,原型项工厂将使用一个原型类型的克隆重新填充模型。为了防止出现这种情况,项的类型标识符必须暴露以进行序列化,并提供一种在反序列化时重建正确类型的项的方法。
让我们看看如何实现标准项模型的一种方式。原型多态项类可以通过数据角色来公开其类型。在设置此角色时,它应使用正确的类型重新创建自身。
false
。// https://github.com/KubaO/stackoverflown/tree/master/questions/model-serialization-32176887
#include <QtGui>
struct BasicTraits {
BasicTraits() {}
/// The base model that the serializer operates on
typedef QAbstractItemModel Model;
/// The streamable representation of model's configuration
typedef bool ModelConfig;
/// The streamable representation of an item's data
typedef QMap<int, QVariant> Roles;
/// The streamable representation of a section of model's header data
typedef Roles HeaderRoles;
/// Returns a streamable representation of an item's data.
Roles itemData(const Model * model, const QModelIndex & index) {
return model->itemData(index);
}
/// Sets the item's data from the streamable representation.
bool setItemData(Model * model, const QModelIndex & index, const Roles & data) {
return model->setItemData(index, data);
}
/// Returns a streamable representation of a model's header data.
HeaderRoles headerData(const Model * model, int section, Qt::Orientation ori) {
Roles data;
data.insert(Qt::DisplayRole, model->headerData(section, ori));
return data;
}
/// Sets the model's header data from the streamable representation.
bool setHeaderData(Model * model, int section, Qt::Orientation ori, const HeaderRoles & data) {
return model->setHeaderData(section, ori, data.value(Qt::DisplayRole));
}
/// Should horizontal header data be serialized?
bool doHorizontalHeaderData() const { return true; }
/// Should vertical header data be serialized?
bool doVerticalHeaderData() const { return false; }
/// Sets the number of rows and columns for children on a given parent item.
bool setRowsColumns(Model * model, const QModelIndex & parent, int rows, int columns) {
bool rc = model->insertRows(0, rows, parent);
if (columns > 1) rc = rc && model->insertColumns(1, columns-1, parent);
return rc;
}
/// Returns a streamable representation of the model's configuration.
ModelConfig modelConfig(const Model *) {
return true;
}
/// Sets the model's configuration from the streamable representation.
bool setModelConfig(Model *, const ModelConfig &) {
return true;
}
};
必须实现这样一个类来满足特定模型的要求。上面给出的类通常对于基本模型已经足够。序列化器实例接受或默认构造特征类的实例。因此,特征可以具有状态。
在处理流和模型操作时,任何一方都可能失败。一个状态
类捕获流和模型是否正常以及是否可以继续操作。当在初始状态中设置了忽略模型故障
时,特征类报告的故障将被忽略,并且加载将继续进行。 QDataStream
的故障总是会中止保存/加载操作。
struct Status {
enum SubStatus { StreamOk = 1, ModelOk = 2, IgnoreModelFailures = 4 };
QFlags<SubStatus> flags;
Status(SubStatus s) : flags(StreamOk | ModelOk | s) {}
Status() : flags(StreamOk | ModelOk) {}
bool ok() const {
return (flags & StreamOk && (flags & IgnoreModelFailures || flags & ModelOk));
}
bool operator()(QDataStream & str) {
return stream(str.status() == QDataStream::Ok);
}
bool operator()(Status s) {
if (flags & StreamOk && ! (s.flags & StreamOk)) flags ^= StreamOk;
if (flags & ModelOk && ! (s.flags & ModelOk)) flags ^= ModelOk;
return ok();
}
bool model(bool s) {
if (flags & ModelOk && !s) flags ^= ModelOk;
return ok();
}
bool stream(bool s) {
if (flags & StreamOk && !s) flags ^= StreamOk;
return ok();
}
};
false
。这将使得序列化代码更容易阅读,因为每个if (!st(...)) return st
惯用语都将被简单的st(...)
所取代。然而,我选择不使用异常,因为典型的Qt代码并不使用它们。要完全消除检测特性方法和流故障的语法开销,需要在特性方法中抛出,而不是返回false
,并使用在失败时抛出的流包装器。bool hasChildren(parent)
int rowCount(parent)
int columnCount(parent)
QModelIndex index(row, column, parent)
template <class Tr = BasicTraits> class ModelSerializer {
enum ItemType { HasData = 1, HasChildren = 2 };
Q_DECLARE_FLAGS(ItemTypes, ItemType)
Tr m_traits;
每个方向的标题都是根据根项行/列计数进行序列化的。
Status saveHeaders(QDataStream & s, const typename Tr::Model * model, int count, Qt::Orientation ori) {
Status st;
if (!st(s << (qint32)count)) return st;
for (int i = 0; i < count; ++i)
if (!st(s << m_traits.headerData(model, i, ori))) return st;
return st;
}
Status loadHeaders(QDataStream & s, typename Tr::Model * model, Qt::Orientation ori, Status st) {
qint32 count;
if (!st(s >> count)) return st;
for (qint32 i = 0; i < count; ++i) {
typename Tr::HeaderRoles data;
if (!st(s >> data)) return st;
if (!st.model(m_traits.setHeaderData(model, i, ori, data))) return st;
}
return st;
}
Status saveData(QDataStream & s, const typename Tr::Model * model, const QModelIndex & parent) {
Status st;
ItemTypes types;
if (parent.isValid()) types |= HasData;
if (model->hasChildren(parent)) types |= HasChildren;
if (!st(s << (quint8)types)) return st;
if (types & HasData) s << m_traits.itemData(model, parent);
if (! (types & HasChildren)) return st;
auto rows = model->rowCount(parent);
auto columns = model->columnCount(parent);
if (!st(s << (qint32)rows << (qint32)columns)) return st;
for (int i = 0; i < rows; ++i)
for (int j = 0; j < columns; ++j)
if (!st(saveData(s, model, model->index(i, j, parent)))) return st;
return st;
}
Status loadData(QDataStream & s, typename Tr::Model * model, const QModelIndex & parent, Status st) {
quint8 rawTypes;
if (!st(s >> rawTypes)) return st;
ItemTypes types { rawTypes };
if (types & HasData) {
typename Tr::Roles data;
if (!st(s >> data)) return st;
if (!st.model(m_traits.setItemData(model, parent, data))) return st;
}
if (! (types & HasChildren)) return st;
qint32 rows, columns;
if (!st(s >> rows >> columns)) return st;
if (!st.model(m_traits.setRowsColumns(model, parent, rows, columns))) return st;
for (int i = 0; i < rows; ++i)
for (int j = 0; j < columns; ++j)
if (!st(loadData(s, model, model->index(i, j, parent), st))) return st;
return st;
}
public:
ModelSerializer() {}
ModelSerializer(const Tr & traits) : m_traits(traits) {}
ModelSerializer(Tr && traits) : m_traits(std::move(traits)) {}
ModelSerializer(const ModelSerializer &) = default;
ModelSerializer(ModelSerializer &&) = default;
Status save(QDataStream & stream, const typename Tr::Model * model) {
Status st;
auto version = stream.version();
stream.setVersion(QDataStream::Qt_5_4);
if (!st(stream << (quint8)0)) return st; // format
if (!st(stream << m_traits.modelConfig(model))) return st;
if (!st(saveData(stream, model, QModelIndex()))) return st;
auto hor = m_traits.doHorizontalHeaderData();
if (!st(stream << hor)) return st;
if (hor && !st(saveHeaders(stream, model, model->rowCount(), Qt::Horizontal))) return st;
auto ver = m_traits.doVerticalHeaderData();
if (!st(stream << ver)) return st;
if (ver && !st(saveHeaders(stream, model, model->columnCount(), Qt::Vertical))) return st;
stream.setVersion(version);
return st;
}
Status load(QDataStream & stream, typename Tr::Model * model, Status st = Status()) {
auto version = stream.version();
stream.setVersion(QDataStream::Qt_5_4);
quint8 format;
if (!st(stream >> format)) return st;
if (!st.stream(format == 0)) return st;
typename Tr::ModelConfig config;
if (!st(stream >> config)) return st;
if (!st.model(m_traits.setModelConfig(model, config))) return st;
if (!st(loadData(stream, model, QModelIndex(), st))) return st;
bool hor;
if (!st(stream >> hor)) return st;
if (hor && !st(loadHeaders(stream, model, Qt::Horizontal, st))) return st;
bool ver;
if (!st(stream >> ver)) return st;
if (ver && !st(loadHeaders(stream, model, Qt::Vertical, st))) return st;
stream.setVersion(version);
return st;
}
};
使用基本特征保存/加载模型:
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
QStringList srcData;
for (int i = 0; i < 1000; ++i) srcData << QString::number(i);
QStringListModel src {srcData}, dst;
ModelSerializer<> ser;
QByteArray buffer;
QDataStream sout(&buffer, QIODevice::WriteOnly);
ser.save(sout, &src);
QDataStream sin(buffer);
ser.load(sin, &dst);
Q_ASSERT(srcData == dst.stringList());
}
QDataStream &operator<<(QDataStream &out, const YourType &t);
QDataStream &operator>>(QDataStream &in, YourType &t);
按照这种模式,您的类型将能够与Qt的容器类“即插即用”。
QAbstractItemModel
不会(或不应该)直接持有数据,它只是底层数据结构的包装器。该模型仅用于为视图提供访问数据的接口。因此,实际上您不应序列化实际模型,而是底层数据。
关于如何序列化实际数据,这取决于您的数据格式,目前仍然是一个谜。但由于它是QAbstractItemModel
,我假设它是某种树形结构,因此一般来说,您必须遍历树并序列化其中的每个对象。
请注意,当序列化单个对象时,序列化和反序列化是一个盲目的过程,但处理对象集合时,您可能需要考虑其结构并使用额外的序列化数据。如果您的树形结构类似于数组的数组,只要使用Qt的容器类,这将为您处理,您只需要实现项目类型的序列化,但对于自定义树形结构,您必须自己完成。
QAbstractItemModel
方法将其填充吗?如果可以,那么是可能的。否则,除非序列化/反序列化直接作用于您的内部数据,否则不可能。 - Kuba hasn't forgotten Monica