我正在使用Qt QGraphicsScene
类,添加预定义的项目,例如QGraphicsRectItem
,QGraphicsLineItem
等,我想将场景内容序列化到磁盘。但是,基类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(,,,));
但这并没有真正帮助我,因为以下情况会崩溃:
QList<QGraphicsItem*> list = scene->items();
foreach (QGraphicsItem* item, items)
{
Item* myitem = reinterpret_class<Item*>(item);
myitem->serialize(...) // FAIL
}
我能以任何方式完成这项工作吗?
答案 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 restart
与QGraphicsItem
接口。以下是处理基本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文档
但我支持斯蒂芬斯回答,你可能想考虑将实际数据拉出场景并将其放在一个单独的模型类中