序列化QGraphicsScene内容

时间:2010-06-08 18:01:36

标签: c++ qt

我正在使用Qt QGraphicsScene类,添加预定义的项目,例如QGraphicsRectItemQGraphicsLineItem等,我想将场景内容序列化到磁盘。但是,基类QGraphicsItem类(我使用的其他项派生自)不支持序列化,因此我需要自己编写代码。问题是所有对这些对象的访问都是通过基类QGraphicsItem指针进行的,因此我所拥有的序列化代码非常糟糕:

QGraphicsScene* scene = new QGraphicsScene;
scene->addRect(QRectF(0, 0, 100, 100));
scene->addLine(QLineF(0, 0, 100, 100));
...
QList<QGraphicsItem*> list = scene->items();
foreach (QGraphicsItem* item, items)
{
  if (item->type() == QGraphicsRectItem::Type)
  {
    QGraphicsRectItem* rect = qgraphicsitem_cast<QGraphicsRectItem*>(item);
    // Access QGraphicsRectItem members here
  }
  else if (item->type() == QGraphicsLineItem::Type)
  {
    QGraphicsLineItem* line = qgraphicsitem_cast<QGraphicsLineItem*>(item);
    // Access QGraphicsLineItem members here
  }
  ...
}

这不是好的代码恕我直言。所以,我可以像这样创建一个ABC类:

class Item
{
public:
  virtual void serialize(QDataStream& strm, int version) = 0;
};

class Rect : public QGraphicsRectItem, public Item
{
public:
  void serialize(QDataStream& strm, int version)
  {
    // Serialize this object
  }
  ...
};

然后我可以使用QGraphicsScene::addItem(new Rect(,,,));

添加Rect对象

但这并没有真正帮助我,因为以下情况会崩溃:

QList<QGraphicsItem*> list = scene->items();
foreach (QGraphicsItem* item, items)
{
  Item* myitem = reinterpret_class<Item*>(item);
  myitem->serialize(...) // FAIL
}

我能以任何方式完成这项工作吗?

4 个答案:

答案 0 :(得分:3)

我同意其他海报,QGraphicsItem可以真正被视为一个视图项,因此将模型数据分成它自己的类可能会更好。

那就是说,我认为你的崩溃是由不适当的演员造成的。

如果您执行以下操作:

Rect *r = new Rect();
QGraphicsItem *qi = reinterpret_cast<QGraphicsItem*>(r);
QGraphicsRectItem *qr = reinterpret_cast<QGraphicsRectItem*>(r);
Item *i = reinterpret_cast<Item*>(r);
qDebug("r = %p, qi = %p, qr = %p, i = %p", r, qi, qr, i);

你应该看到r == qi,r == qr,但r!= i。如果你考虑一个乘法继承的对象如何在内存中表示,第一个基类首先在内存中,第二个基类是第二个,依此类推。因此,指向第二个基类的指针将偏移[大约]第一个基类的大小。

因此,为了修复您的代码,我认为您需要执行以下操作:

QList<QGraphicsItem*> list = scene->items();
foreach (QGraphicsItem* item, items)
{
  Rect* myrect = reinterpret_class<Rect*>(item);  // needed to figure out the offset to the Item part
  Item* myitem = reinterpret_class<Item*>(myrect);
  myitem->serialize(...);
}

这是众多原因之一,我希望尽可能避免多重继承。我强烈建议您按照之前的建议将模型数据分开。

答案 1 :(得分:1)

序列化QGraphicsItem不是一个好主意。 QGrahpicsScene应该代表您的数据。不是序列化表示,最好是序列化应用程序的数据/模型。

如果您想记录图形实体的排列,也许您可​​以使用自定义QGraphicsItem并绘制成QPicture

答案 2 :(得分:1)

可以利用Qt元类型系统将sudo service apache2 restartQGraphicsItem接口。以下是处理基本Qt QDataStream派生类型以及对象层次结构的完整示例-即子项也被保存和还原。该接口非常小-下面是支持某些常见类型存储所需要的标头。

添加自定义类型只需在界面中添加一行QGraphicsItem

DECLARE_GRAPHICSITEM_METATYPE(CustomItemType)

通过// https://github.com/KubaO/stackoverflown/tree/master/questions/qgraphicsitem-stream-51492181 // Interface #include <QDataStream> #include <QGraphicsItem> #include <QGraphicsTransform> #include <QMetaType> #include <algorithm> #include <type_traits> template <class B, class T> struct NonConstructibleFunctionHelper { static void *Construct(void *, const void *) { return {}; } static void Destruct(void *t) { static_cast<T *>(static_cast<B *>(t))->~T(); } static void Save(QDataStream &ds, const void *t) { ds << *static_cast<const T *>(static_cast<const B *>(t)); } static void Load(QDataStream &ds, void *t) { ds >> *static_cast<T *>(static_cast<B *>(t)); } }; template <class B, class T> struct NonCopyableFunctionHelper : NonConstructibleFunctionHelper<B, T> { static void *Construct(void *where, const void *t) { return (!t) ? static_cast<B *>(new (where) T) : nullptr; } }; #define DECLARE_POLYMORPHIC_METATYPE(BASE_TYPE, TYPE) \ DECLARE_POLYMORPHIC_METATYPE_IMPL(BASE_TYPE, TYPE) #define DECLARE_POLYMORPHIC_METATYPE_IMPL(BASE_TYPE, TYPE) \ QT_BEGIN_NAMESPACE namespace QtMetaTypePrivate { \ template <> \ struct QMetaTypeFunctionHelper<TYPE> \ : std::conditional< \ std::is_copy_constructible<TYPE>::value, void, \ std::conditional< \ std::is_default_constructible<TYPE>::value, \ NonCopyableFunctionHelper<BASE_TYPE, TYPE>, \ NonConstructibleFunctionHelper<BASE_TYPE, TYPE>>::type>::type {}; \ QT_END_NAMESPACE \ } \ Q_DECLARE_METATYPE_IMPL(TYPE) #define DECLARE_POLYSTREAMING_METATYPE(BASE_TYPE, TYPE) \ DECLARE_POLYSTREAMING_METATYPE_IMPL(BASE_TYPE, TYPE) #define DECLARE_POLYSTREAMING_METATYPE_IMPL(BASE_TYPE, TYPE) \ DECLARE_POLYMORPHIC_METATYPE_IMPL(BASE_TYPE, TYPE) \ QDataStream &operator<<(QDataStream &, const TYPE &); \ QDataStream &operator>>(QDataStream &, TYPE &); #define DECLARE_GRAPHICSITEM_METATYPE(TYPE) \ DECLARE_POLYSTREAMING_METATYPE_IMPL(QGraphicsItem, TYPE) QDataStream &operator<<(QDataStream &, const QList<QGraphicsItem *> &); void saveProperties(QDataStream &, const QObject *, const QByteArrayList &excluded = {}); void loadProperties(QDataStream &, QObject *); QDataStream &operator<<(QDataStream &, const QGraphicsTransform *); QDataStream &operator>>(QDataStream &, QGraphicsTransform *&); void registerMapping(int typeId, int itemType); template <typename T> typename std::enable_if<std::is_base_of<QGraphicsItem, T>::value>::type registerMapping() { qRegisterMetaTypeStreamOperators<T>(); if (!std::is_base_of<QGraphicsObject, T>::value) // The QObject-derived types don't need such mappings. registerMapping(qMetaTypeId<T>(), T::Type); } QDataStream &operator<<(QDataStream &, const QGraphicsItem *); QDataStream &operator>>(QDataStream &, QGraphicsItem *&); DECLARE_POLYMORPHIC_METATYPE(QGraphicsTransform, QGraphicsRotation) DECLARE_POLYMORPHIC_METATYPE(QGraphicsTransform, QGraphicsScale) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsObject) DECLARE_GRAPHICSITEM_METATYPE(QAbstractGraphicsShapeItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsItemGroup) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsLineItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsPixmapItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsEllipseItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsPathItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsPolygonItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsRectItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsSimpleTextItem) DECLARE_GRAPHICSITEM_METATYPE(QGraphicsTextItem) class ItemStream { QDataStream &ds; QGraphicsItem &item; public: using Version = qint8; static constexpr Version CurVersion = 1; enum { VersionKey = 0x9000 }; ItemStream(QDataStream &ds, class QGraphicsItem &item) : ds(ds), item(item) {} template <typename C, typename T> ItemStream &operator>>(void (C::*set)(T)) { using decayed_type = typename std::decay<T>::type; using value_type = typename std::conditional<std::is_enum<decayed_type>::value, std::underlying_type<decayed_type>, std::decay<T>>::type::type; value_type value; ds >> value; (static_cast<C &>(item).*set)(static_cast<T>(value)); return *this; } static Version formatOf(const QGraphicsItem &item) { auto version = item.data(VersionKey); return Version(version.isValid() ? version.value<int>() : 0); } static void setVersion(QGraphicsItem &item, Version version) { item.setData(VersionKey, version); } }; 类,可以更轻松地将其流式传输到属性设置器中,并且在实现流式运算符时非常有用。它允许只写ItemStream而不是itemStream >> &Class::setFoo

此界面允许编写以下示例-从this answer扩展。

type foo; dataStream >> foo; obj.setFoo(foo);

具体类型的专业化非常简单。对于每个// Test #include <QtWidgets> QPixmap makePixmap() { QPixmap pix(100, 50); pix.fill(Qt::transparent); QPainter p(&pix); p.setPen(Qt::darkCyan); p.setFont({"helvetica,arial", 15}); p.drawText(pix.rect(), Qt::AlignCenter, "Hello!"); return pix; } int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget ui; QGridLayout layout(&ui); QGraphicsScene scene; QGraphicsView view(&scene); QPushButton save("Save"); QPushButton load("Load"); layout.addWidget(&view, 0, 0, 1, 2); layout.addWidget(&load, 1, 0); layout.addWidget(&save, 1, 1); auto *eitem = scene.addEllipse(QRect(10, 10, 80, 50), QPen(Qt::green), QBrush(Qt::black)); auto *xform = new QGraphicsRotation; xform->setAngle(10); eitem->setPos(100, 10); eitem->setRotation(60); eitem->setTransformations({xform}); auto *litem = scene.addLine(QLineF(0, 0, 100, 100), QPen(Qt::red)); litem->setPos(10, 10); litem->setRotation(100); scene.createItemGroup({eitem, litem}); auto *ritem = scene.addRect(QRect(10, 0, 100, 100), QPen(Qt::blue), QBrush(Qt::red)); ritem->setPos(10, 100); ritem->setRotation(10); QPainterPath path; path.moveTo(100, 100); path.lineTo(10, 0); path.addRect(QRect(0, 0, 100, 22)); auto *pitem = scene.addPath(path, QPen(Qt::green), QBrush(Qt::black)); pitem->setPos(100, 22); pitem->setRotation(120); scene.addPixmap(makePixmap()); auto *stitem = scene.addSimpleText("Simple Text", {"arial", 17}); stitem->setPos(-50, -10); stitem->setPen(QPen(Qt::darkGreen)); auto *titem = scene.addText("Text", {"arial", 17, true}); titem->setPos(-100, 0); titem->setDefaultTextColor(Qt::darkYellow); auto const flags = QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable; for (auto *it : scene.items()) it->setFlags(flags); QByteArray data; QObject::connect(&save, &QPushButton::clicked, [&scene, &data]() { qDebug() << "writing ..."; QBuffer dev(&data); if (dev.open(QIODevice::WriteOnly)) { QDataStream out(&dev); out << scene.items(Qt::AscendingOrder); scene.clear(); qDebug() << "done writing"; } }); QObject::connect(&load, &QPushButton::clicked, [&scene, &data]() { qDebug() << "reading ..."; QBuffer dev(&data); if (dev.open(QIODevice::ReadOnly)) { QList<QGraphicsItem *> items; QDataStream in(&dev); in >> items; for (auto *item : items) scene.addItem(item); qDebug() << "done reading"; } }); ui.show(); return a.exec(); } 声明,您都必须实现DECLARE_GRAPHICSITEM_METATYPE(ItemType)QDataStream &operator>>(QDataStream &in, ItemType &g)。这些流运算符必须首先为直接基类调用该运算符。您还必须注册映射-可以在QDataStream &operator<<(QDataStream &out, const ItemType &g)或静态初始化程序中完成。

这些实现受到this answer的启发。

main

实现的核心实现了元类型类型名称和// Implementation Specializations static bool specInit = [] { qRegisterMetaType<QGraphicsRotation>(); qRegisterMetaType<QGraphicsScale>(); registerMapping<QGraphicsItemGroup>(); registerMapping<QGraphicsLineItem>(); registerMapping<QGraphicsPixmapItem>(); registerMapping<QGraphicsEllipseItem>(); registerMapping<QGraphicsPathItem>(); registerMapping<QGraphicsPolygonItem>(); registerMapping<QGraphicsRectItem>(); registerMapping<QGraphicsSimpleTextItem>(); registerMapping<QGraphicsTextItem>(); return true; }(); QDataStream &operator<<(QDataStream &out, const QGraphicsEllipseItem &g) { out << static_cast<const QAbstractGraphicsShapeItem &>(g); out << g.rect() << g.startAngle() << g.spanAngle(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsEllipseItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QAbstractGraphicsShapeItem &>(g); ItemStream(in, g) >> &QGI::setRect >> &QGI::setStartAngle >> &QGI::setSpanAngle; return in; } QDataStream &operator<<(QDataStream &out, const QGraphicsPathItem &g) { out << static_cast<const QAbstractGraphicsShapeItem &>(g); out << g.path(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsPathItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QAbstractGraphicsShapeItem &>(g); ItemStream(in, g) >> &QGI::setPath; return in; } QDataStream &operator<<(QDataStream &out, const QGraphicsPolygonItem &g) { out << static_cast<const QAbstractGraphicsShapeItem &>(g); out << g.polygon() << g.fillRule(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsPolygonItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QAbstractGraphicsShapeItem &>(g); ItemStream(in, g) >> &QGI::setPolygon >> &QGI::setFillRule; return in; } QDataStream &operator<<(QDataStream &out, const QGraphicsRectItem &g) { out << static_cast<const QAbstractGraphicsShapeItem &>(g); out << g.rect(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsRectItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QAbstractGraphicsShapeItem &>(g); ItemStream(in, g) >> &QGI::setRect; return in; } QDataStream &operator<<(QDataStream &out, const QGraphicsSimpleTextItem &g) { out << static_cast<const QAbstractGraphicsShapeItem &>(g); out << g.text() << g.font(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsSimpleTextItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QAbstractGraphicsShapeItem &>(g); ItemStream(in, g) >> &QGI::setText >> &QGI::setFont; return in; } QDataStream &operator<<(QDataStream &out, const QGraphicsItemGroup &g) { return out << static_cast<const QGraphicsItem &>(g); } QDataStream &operator>>(QDataStream &in, QGraphicsItemGroup &g) { return in >> static_cast<QGraphicsItem &>(g); } QDataStream &operator<<(QDataStream &out, const QGraphicsLineItem &g) { out << static_cast<const QGraphicsItem &>(g); out << g.pen() << g.line(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsLineItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QGraphicsItem &>(g); ItemStream(in, g) >> &QGI::setPen >> &QGI::setLine; return in; } QDataStream &operator<<(QDataStream &out, const QGraphicsPixmapItem &g) { out << static_cast<const QGraphicsItem &>(g); out << g.pixmap() << g.offset() << g.transformationMode() << g.shapeMode(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsPixmapItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QGraphicsItem &>(g); ItemStream(in, g) >> &QGI::setPixmap >> &QGI::setOffset >> &QGI::setTransformationMode >> &QGI::setShapeMode; return in; } QDataStream &operator<<(QDataStream &out, const QGraphicsTextItem &g) { out << static_cast<const QGraphicsObject &>(g) << g.font() << g.textWidth() << g.defaultTextColor() << g.toHtml() << g.tabChangesFocus() << g.textInteractionFlags(); return out; } QDataStream &operator>>(QDataStream &in, QGraphicsTextItem &g) { using QGI = std::decay<decltype(g)>::type; in >> static_cast<QGraphicsObject &>(g); ItemStream(in, g) >> &QGI::setFont >> &QGI::setTextWidth >> &QGI::setDefaultTextColor >> &QGI::setHtml >> &QGI::setTabChangesFocus >> &QGI::setTextInteractionFlags; return in; } 项类型之间的映射。否则,对于从QGraphicsItem::type()派生的类型(即QObject),它将直接使用类型名称。它还可以处理没有重复项目的保存项目列表。

QGraphicsObject

答案 3 :(得分:0)

如果您真的想序列化这些项目,请创建

QDataStream &operator<<(QDataStream &, const AppropriateSubclass&);
QDataStream &operator>>(QDataStream &, AppropriateSubclass&);

对于您要序列化的每个QGraphicsItem子类,这些函数都是非成员函数。该方法也在QDataStream文档

中进行了解释

但我支持斯蒂芬斯回答,你可能想考虑将实际数据拉出场景并将其放在一个单独的模型类中