如何将QAbstractItemModel序列化为QDataStream? [英] How to serialize a QAbstractItemModel into QDataStream?

查看:147
本文介绍了如何将QAbstractItemModel序列化为QDataStream?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经建立了一个QAbstractItemModel并用数据填充了它.我的QTreeView小部件可以正确显示该模型中的每个数据.

I've set up a QAbstractItemModel and filled that with data. My QTreeView widget displays every data in that model properly.

现在,我想将序列化的模型存储在一个二进制文件中(稍后将这些二进制文件恢复到模型中).有可能吗?

Now, I would like to store that model serialized in a binary file (and later of cource load that binary file back into a model). Is that possible?

推荐答案

模型序列化的细节在某种程度上取决于模型的实现.一些陷阱包括:

The particulars of model serialization depend somewhat on the model's implementation. Some gotchas include:

  1. 完全可用的模型可能未实现insertRows/insertColumns,而是改用自定义方法.

  1. Perfectly usable models might not implement insertRows/insertColumns, preferring to use custom methods instead.

QStandardItemModel这样的模型可能具有不同类型的基础项.反序列化后,原型项目工厂将使用一种原型类型的克隆重新填充模型.为防止这种情况,必须公开商品的类型标识符以进行序列化,并提供一种在反序列化时重建正确类型的商品的方法.

Models like QStandardItemModel may have underlying items of varying types. Upon deserialization, the prototype item factory will repopulate the model with clones of one prototype type. To prevent that, the items't type identifier must be exposed for serialization, and a way provided to rebuild the item of a correct type upon deserialization.

让我们看看为标准项目模型实现它的一种方法.原型多态项目类可以通过数据角色公开其类型.设置此角色后,它应该使用正确的类型重新创建自己.

Let's see one way of implementing it for the standard item model. The prototype polymorphic item class can expose its type via a data role. Upon setting this role, it should re-create itself with a correct type.

鉴于此,通用串行器是不可行的.

Given this, a universal serializer isn't feasible.

那么,让我们看一个完整的示例.给定模型类型必需的行为必须由参数化序列化器的特征类来表示.从模型读取数据的方法采用恒定的模型指针.修改模型的方法采用非恒定的模型指针,并在失败时返回false.

Let's look at a complete example, then. The behaviors necessary for a given model type must be represented by a traits class that parametrizes the serializer. The methods reading data from the model take a constant model pointer. The methods modifying the model take a non-constant model pointer, and return false upon failure.

// 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;
    }
};

必须实现此类以捕获特定模型的需求.上面给出的一个对于基本模型通常就足够了.序列化程序实例采用或默认构造traits类的实例.因此,特质可以具有状态.

Such a class must be implemented to capture the requirements of a particular model. The one given above is often sufficient for basic models. A serializer instance takes or default-constructs an instance of the traits class. Thus, traits can have state.

在处理流和模型操作时,任何一个都可能失败. Status类捕获流和模型是否正常以及是否可以继续.如果将IgnoreModelFailures设置为初始状态,则traits类报告的失败将被忽略,尽管如此,加载仍在继续. QDataStream失败总是会中止保存/加载.

When dealing with streaming and model operations, either can fail. A Status class captures whether the stream and model are ok, and whether it's possible to continue. When IgnoreModelFailures is set on the initial status, the failures reported by the traits class are ignored and the loading proceeds in spite of them. QDataStream failures always abort the save/load.

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代码不使用它们.为了完全消除检测traits方法和流失败的语法开销,需要投入traits方法而不是返回false,并使用引发失败的流包装器.

This class could also be implemented to throw itself as an exception instead of returning false. This would make the serializer code a bit easier to read, as every if (!st(...)) return st idiom would be replaced by simpler st(...). Nevertheless, I chose not to use exceptions, as typical Qt code doesn't use them. To completely remove the syntax overhead of detecting traits methods and stream failures, one would need to throw in the traits methods instead of returning false, and use a stream wrapper that throws on failure.

最后,我们有一个通用序列化器,由traits类参数化.大多数模型操作都委托给traits类.直接在模型上执行的几个操作是:

Finally, we have a generic serializer, parametrized by a traits class. The majority of model operations are delegated to the traits class. The few operations performed directly on the model are:

  • bool hasChildren(parent)
  • int rowCount(parent)
  • int columnCount(parent)
  • QModelIndex index(row, column, parent)
  • 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;

每个方向的标题均根据根项目的行/列计数进行序列化.

Headers for each orientation are serialized based on the root item row/column counts.

    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;
    }

每个项目的数据都以递归的顺序进行序列化,顺序是深度优先,行前列.任何物品都可以有孩子.项目标志未序列化;理想情况下,应该在特征中对这种行为进行参数化.

The data for each item is serialized recursively, ordered depth-first, columns-before-rows. Any item can have children. Item flags are not serialized; ideally this behavior should be parametrized in the traits.

    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;
    }

序列化程序保留一个traits实例,也可以将其传递给使用.

The serializer retains a traits instance, it can also be passed one to use.

public:
    ModelSerializer() {}
    ModelSerializer(const Tr & traits) : m_traits(traits) {}
    ModelSerializer(Tr && traits) : m_traits(std::move(traits)) {}
    ModelSerializer(const ModelSerializer &) = default;
    ModelSerializer(ModelSerializer &&) = default;

数据按以下顺序序列化:

The data is serialized in following order:

  1. 模型配置,
  2. 模型数据
  3. 水平标题数据
  4. 垂直标题数据.

请注意流和流数据的版本控制.

Attention is paid to versioning of both the stream and the streamed data.

    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;
    }
};

要使用基本特征保存/加载模型,请执行以下操作:

To save/load a model using the basic traits:

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());
}

这篇关于如何将QAbstractItemModel序列化为QDataStream?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆