Advanced SVG image usage in QML
-
Hi all,
I know I can display SVG image using QImage item in QML but I'm trying to display SVG image with multiple SVG elements and attach to some of them mouse events.
I thought using QGraphicSvgItem class will be enough for me and done some example code:class SVGImage : public QQuickPaintedItem void SVGImage::paint(QPainter *painter) { qDebug() << "SVGImage::paint"; QGraphicsSvgItem svgItem{QLatin1String("img.svg")}; svgItem.setElementId("svg_34"); svgItem.renderer()->render(painter); }
and the image is being painted but it looks like whatever I'll set up into svgItem it will be ignored - whole image is being painted.
I'm rather newbie in Qt's painting stuff, there is a lot of options and dependencies so to make it a little bit shorter I need some tips which classes should I learn to do this correctly.All I need is to create C++ implementation for QML object which will paint SVG image and to each SVG item it will attach mouse event (separately so I'll be able to find out which item has been clicked) and manage to handle this event correctly.
Or maybe there is any public interface which is responsible for parsing SVG file? I've found some internal implementations but due to further problems with fixing issues triggered by changes in internal API I'm rather far from using it.
Thanks in advance!
-
Hi all,
I know I can display SVG image using QImage item in QML but I'm trying to display SVG image with multiple SVG elements and attach to some of them mouse events.
I thought using QGraphicSvgItem class will be enough for me and done some example code:class SVGImage : public QQuickPaintedItem void SVGImage::paint(QPainter *painter) { qDebug() << "SVGImage::paint"; QGraphicsSvgItem svgItem{QLatin1String("img.svg")}; svgItem.setElementId("svg_34"); svgItem.renderer()->render(painter); }
and the image is being painted but it looks like whatever I'll set up into svgItem it will be ignored - whole image is being painted.
I'm rather newbie in Qt's painting stuff, there is a lot of options and dependencies so to make it a little bit shorter I need some tips which classes should I learn to do this correctly.All I need is to create C++ implementation for QML object which will paint SVG image and to each SVG item it will attach mouse event (separately so I'll be able to find out which item has been clicked) and manage to handle this event correctly.
Or maybe there is any public interface which is responsible for parsing SVG file? I've found some internal implementations but due to further problems with fixing issues triggered by changes in internal API I'm rather far from using it.
Thanks in advance!
@osmial
QGraphics* classes are actually part of the graphics view framework and shouldn't be used outside of a QGraphicsView/QGraphicsScene.You should rather use QSvgRenderer instead.
But when i undertsand you correctly you want interaction for each element inside a SVG file? I am not aware of such a suffisticated class in Qt.
You could parse the SVG file for the elements using Qt's xml classes and add them separatley.Or i guess it's probably even better to write a custom class/widget which parses the SVG and caches it's element properties and process the mouse events accordingly. Handle the repaints on every change and write out a valid svg after the changes.
-
Thanks @raven-worx for your response.
I've already used QSvgRenderer but was looking for more functionalities so thought I should use QGraphicsSvgItem cause in Qt's documentation it is listed next to QSvgRenderer class...I thought there is some SVG parser provided as a public API (i.e. QImage objects are being parsed and displayed somehow), but it looks like I'll need to use XML parser and code some stuff.
-
I get back to this task and have new challenge.
I've created:class SVGElement : public QQuickItem { public: SVGElement(QRgb color) : _fillColor(color) {} virtual ~SVGElement() {} virtual void draw(QPainter* painter) = 0; QRgb _fillColor; }; class SVGRect : public SVGElement { public: SVGRect(double width, double height, double x, double y, QRgb fillColor) : SVGElement{fillColor}, _width{width}, _height{height}, _x{x}, _y{y} { setAcceptedMouseButtons(Qt::AllButtons); } void draw(QPainter* painter) override { QRectF r{_x, _y, _width, _height}; painter->drawRect(r); } void mousePressEvent(QMouseEvent* event) override { qDebug() << "SVGRect mouse press event"; _fillColor = Qt::black; update(); } private: double _width; double _height; double _x; double _y; };
SVGElement class is a base class for shapes which can be described inside SVG file.
class SVGImage : public QQuickPaintedItem { [...] std::vector<std::unique_ptr<SVGElement>> m_elements; }
The main part is inside SVGImage class where I have vector of SVG elements. I've made some tests and if I add mouse event handler inside SVGImage class then mouse events are being consumed, but if I'm trying to define mouse event handler for SVGRect class (pasted implementation) then mouse events are not being consumed. The main difference is that SVGImage is being instantiated in QML file (SVGImage is QML registered type) and SVGRect isn't. So what is correct approach to attach mouse events to SVGRect class?
-
I get back to this task and have new challenge.
I've created:class SVGElement : public QQuickItem { public: SVGElement(QRgb color) : _fillColor(color) {} virtual ~SVGElement() {} virtual void draw(QPainter* painter) = 0; QRgb _fillColor; }; class SVGRect : public SVGElement { public: SVGRect(double width, double height, double x, double y, QRgb fillColor) : SVGElement{fillColor}, _width{width}, _height{height}, _x{x}, _y{y} { setAcceptedMouseButtons(Qt::AllButtons); } void draw(QPainter* painter) override { QRectF r{_x, _y, _width, _height}; painter->drawRect(r); } void mousePressEvent(QMouseEvent* event) override { qDebug() << "SVGRect mouse press event"; _fillColor = Qt::black; update(); } private: double _width; double _height; double _x; double _y; };
SVGElement class is a base class for shapes which can be described inside SVG file.
class SVGImage : public QQuickPaintedItem { [...] std::vector<std::unique_ptr<SVGElement>> m_elements; }
The main part is inside SVGImage class where I have vector of SVG elements. I've made some tests and if I add mouse event handler inside SVGImage class then mouse events are being consumed, but if I'm trying to define mouse event handler for SVGRect class (pasted implementation) then mouse events are not being consumed. The main difference is that SVGImage is being instantiated in QML file (SVGImage is QML registered type) and SVGRect isn't. So what is correct approach to attach mouse events to SVGRect class?
@osmial
did you set the QuickItem::acceptedMouseButtons()?
If not no mouse events will be delivered to the item. -
@raven-worx,
Thanks for your reply.In SVGRect's constructor I'm calling:
setAcceptedMouseButtons(Qt::AllButtons);
actually you can check it in attached code snippets I've pasted in previous post.
When I've enabled mouse events in SVGImage's constructor and then clicked somewhere in app on SVGImage's area mouse events have been received properly (at least mousePressEvent - but it's enough for me), but SVGImage is a container for SVGElements (SVGRect and others derives from SVGElement class). And it looks for me like those elements which I put into vector haven't been registered somewhere in QML's engine and mouse events are not being sent to them.
-
@raven-worx,
Thanks for your reply.In SVGRect's constructor I'm calling:
setAcceptedMouseButtons(Qt::AllButtons);
actually you can check it in attached code snippets I've pasted in previous post.
When I've enabled mouse events in SVGImage's constructor and then clicked somewhere in app on SVGImage's area mouse events have been received properly (at least mousePressEvent - but it's enough for me), but SVGImage is a container for SVGElements (SVGRect and others derives from SVGElement class). And it looks for me like those elements which I put into vector haven't been registered somewhere in QML's engine and mouse events are not being sent to them.
@osmial said in Advanced SVG image usage in QML:
And it looks for me like those elements which I put into vector haven't been registered somewhere in QML's engine and mouse events are not being sent to them.
when you insert them into the vector do you also set their parent item to the SVGImage instance?
If not you just have the item instances in the memory, but nowhere in the QML space, so then they of course cannot receive the events. -
@raven-worx cause I'm far from my code I've created simple example without any SVG and other unneeded logic.
Here's the code:
#ifndef IMAGE_H #define IMAGE_H #include <vector> #include <memory> #include <QQuickPaintedItem> #include <QPainter> #include <QMouseEvent> #include <QDebug> using namespace std; class ImgElement : public QQuickItem { Q_OBJECT public: ImgElement(QQuickItem* parent, QColor fillClr) : QQuickItem{parent}, _fillClr{fillClr} { setAcceptedMouseButtons(Qt::AllButtons); } virtual ~ImgElement() {} virtual void draw(QPainter* painter) {} void mousePressEvent(QMouseEvent* event) override { qDebug() << "mouse press event"; _fillClr = Qt::black; update(); } QColor color() { return _fillClr; } protected: QColor _fillClr; }; class RectImgElement : public ImgElement { public: RectImgElement(QQuickItem* parent, int x, int y, int w, int h, QColor fillClr) : ImgElement{parent, fillClr}, _r{x, y, w, h} {} void draw(QPainter* painter) override { painter->drawRect(_r); } private: QRect _r; }; class Image : public QQuickPaintedItem { Q_OBJECT public: Image(QQuickPaintedItem* parent = 0) : QQuickPaintedItem{parent} { _elements.emplace_back(new RectImgElement{ this, 50, 50, 50, 50, Qt::red}); } void paint(QPainter *painter) { for(auto&& e : _elements) { QBrush b{e->color()}; painter->setBrush(b); e->draw(painter); } } signals: public slots: private: vector<unique_ptr<ImgElement>> _elements; }; #endif // IMAGE_H
In this case I'm setting parent for each ImgElement in it's constructor to Image (container). Also in ImgElement's ctor I'm enabling mouse events but still failing to receive mouse event in ImgElement.
-
Actually the same story is when instead of receiving mouse events we will change:
class ImgElement : public QQuickItem
to
class ImgElement : public QQuickPaintedItem
and of course will override paint method for them. The paint method is not being called at all.
So maybe somebody can provide an easy example of C++ backend class which contains container of elements which actually are being painted? -
I found this thread on this forum:
https://forum.qt.io/topic/42986/painting-a-dynamically-created-qquickpainteditem-subclassand it looks like this guy had the same problem as I have.
Finally he solved it but I still have problems. I'm not sure if my approach is correct.I've added this code to RectImgElement's ctor:
setWidth(w); setHeight(h); setSize({width(), height()});
(in few different ways), but my elements ain't still painted...
Can anybody more experienced point me what am I doing wrong?