Unsolved Custom QGraphicsItem - weird event handling in parent and child
-
Hi all :)
This is a re-post of my question on Stackoverflow. It is also a follow-up of an answer I gave a few days ago here (I didn't include it here because the post will become a novel :D). The Qt version that I'm using Qt 5.5 on a 64bit Debian Jessie.
Recently I've started working with
QGraphicsViewer
,QGraphicsScene
andQGraphicsItem
just out of curiosity. I decided to implement my own version ofQGraphicsItem
but also take what's already there whenever I don't want to customize a specific feature of that class.From the link above you can see my original code without the usage of a custom
QGraphicsItem
. The purpose is to create an UI that uses a composite node view allowing an easy and fast creation of image processing pipelines. Each node will be represented by aQGraphicsProxyWidget
as well as 3 customQGraphicsItem
instances each serving a specific purpose. The example I've given in the linked answer of mine I am using an item positioned at the top of the proxy widget, which will be responsible for selecting, moving or deleting the entire node (proxy widgets and all 3QGraphicsItem
s).Below you can see the additional code. I'm at a very early stage of experimenting so things like memory leaks etc. are not of the essence here.
proxytransformitem.hpp
#ifndef PROXYTRANSFORMITEM_HPP #define PROXYTRANSFORMITEM_HPP #include <QGraphicsRectItem> #include <QPainter> #include <QStyleOptionGraphicsItem> #include <QPen> #include <QBrush> #include <QGraphicsSceneHoverEvent> //#include <QRectF> class ProxyTransformItem : public QGraphicsRectItem { qreal w; qreal h; bool hovered; QPen *defaultPen; QBrush *defaultInlineBrush; QBrush *inlineBrushHover; QBrush *inlineBrushSelected; public: explicit ProxyTransformItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = 0); ~ProxyTransformItem(); void setPaintTools(QPen *defaultPen, QBrush *defaultInlineBrush, QBrush *inlineBrushHover, QBrush *inlineBrushSelected); float width(); float height(); protected: virtual QRectF boundingRect() const; virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); virtual QPainterPath shape(); virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event); //virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); }; #endif // PROXYTRANSFORMITEM_HPP
proxytransformitem.cpp
#include "proxytransformitem.hpp" #include <iostream> ProxyTransformItem::ProxyTransformItem(qreal x, qreal y, qreal _width, qreal _height, QGraphicsItem *parent) : QGraphicsRectItem(parent), w(_width), h(_height), hovered(false) { setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setPos(x, y); } ProxyTransformItem::~ProxyTransformItem() { } void ProxyTransformItem::setPaintTools(QPen *_defaultPen, QBrush *_defaultInlineBrush, QBrush *_inlineBrushHover, QBrush *_inlineBrushSelected) { defaultPen = _defaultPen; defaultInlineBrush = _defaultInlineBrush; inlineBrushHover = _inlineBrushHover; inlineBrushSelected = _inlineBrushSelected; } float ProxyTransformItem::width() { return w; } float ProxyTransformItem::height() { return h; } QRectF ProxyTransformItem::boundingRect() const { return QRectF(0, 0, w, h); } void ProxyTransformItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { QRectF rect = boundingRect(); painter->setPen(*defaultPen); if (hovered) painter->setBrush(*inlineBrushHover); else { if(!isSelected()) painter->setBrush(*defaultInlineBrush); else painter->setBrush(*inlineBrushSelected); } // painter->drawRect(rect.x() - defaultPen->width(), rect.y() - defaultPen->width(), w + defaultPen->width(), h + defaultPen->width()); painter->drawRect(rect); } QPainterPath ProxyTransformItem::shape() { QPainterPath path; QRectF rect = boundingRect(); // path.addRect(rect.x() - defaultPen->width(), rect.y() - defaultPen->width(), w + defaultPen->width(), h + defaultPen->width()); path.addRect(rect); return path; } void ProxyTransformItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { hovered = true; prepareGeometryChange(); QGraphicsRectItem::hoverEnterEvent(event); } void ProxyTransformItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { hovered = false; prepareGeometryChange(); QGraphicsRectItem::hoverLeaveEvent(event); } void ProxyTransformItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { // TODO QGraphicsRectItem::mousePressEvent(event); } void ProxyTransformItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { // TODO QGraphicsRectItem::mouseReleaseEvent(event); } void ProxyTransformItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { // TODO QGraphicsRectItem::mouseDoubleClickEvent(event); } void ProxyTransformItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // TODO QGraphicsRectItem::mouseMoveEvent(event); }
scenewithmovableproxies.hpp
#ifndef SCENEWITHMOVABLEPROXIES_HPP #define SCENEWITHMOVABLEPROXIES_HPP #include <QWidget> #include <QPoint> namespace Ui { class SceneWithMovableProxies; } class SceneWithMovableProxies : public QWidget { Q_OBJECT public: explicit SceneWithMovableProxies(QWidget *parent = 0); ~SceneWithMovableProxies(); private: Ui::SceneWithMovableProxies *ui; QGraphicsProxyWidget* addWidgetToScene(QPointF initPos); QPen *defaultPen; QBrush *defaultInlineBrush; QBrush *inlineBrushHover; QBrush *inlineBrushSelected; }; #endif // SCENEWITHMOVABLEPROXIES_HPP
scenewithmovableproxies.cpp
#include "scenewithmovableproxies.hpp" #include "ui_scenewithmovableproxies.h" #include "scenewidgetitem.hpp" #include "proxytransformitem.hpp" #include <QGraphicsProxyWidget> #include <iostream> SceneWithMovableProxies::SceneWithMovableProxies(QWidget *parent) : QWidget(parent), ui(new Ui::SceneWithMovableProxies) { ui->setupUi(this); ui->graphicsView->setRenderHint(QPainter::Antialiasing); ui->graphicsView->setScene(new QGraphicsScene(this)); defaultPen = new QPen(Qt::black); defaultPen->setWidth(.2); defaultInlineBrush = new QBrush(Qt::darkGreen); inlineBrushHover = new QBrush(Qt::red); inlineBrushSelected = new QBrush(Qt::yellow); QGraphicsProxyWidget *proxy1 = addWidgetToScene(QPoint(10, 10)); QGraphicsProxyWidget *proxy2 = addWidgetToScene(QPoint(300, 100)); QGraphicsProxyWidget *proxy3 = addWidgetToScene(QPoint(200, 200)); std::cout << proxy2->collidesWithItem(proxy3) << std::endl; } SceneWithMovableProxies::~SceneWithMovableProxies() { delete defaultPen; delete defaultInlineBrush; delete inlineBrushHover; delete inlineBrushSelected; delete ui; } QGraphicsProxyWidget* SceneWithMovableProxies::addWidgetToScene(QPointF initPos) { SceneWidgetItem *widget = new SceneWidgetItem(); ProxyTransformItem *proxyControl = new ProxyTransformItem(initPos.x(), initPos.y(), widget->width(), 20); ui->graphicsView->scene()->addItem(proxyControl); proxyControl->setPaintTools(defaultPen, defaultInlineBrush, inlineBrushHover, inlineBrushSelected); // QGraphicsRectItem *proxyControl = ui->graphicsView->scene()->addRect(initPos.x(), initPos.y(), widget->width(), 20, QPen(Qt::black), QBrush(Qt::darkGreen)); // proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true); // proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true); QGraphicsProxyWidget * const proxy = ui->graphicsView->scene()->addWidget(widget); proxy->setPos(0, proxyControl->height()); // proxy->setPos(initPos.x(), initPos.y()+proxyControl->rect().height()); proxy->setParentItem(proxyControl); // proxyControl->setRotation(45); return proxy; }
Now to my problem - both my proxy widget (child) and my custom
QGraphicsRectItem
(parent) are doing some weird handling of events:-
In my original code if I use the standard
QGraphicsItem
(and its flavours) things work as expected - I click on theQGraphicsItem
(not theQGraphicsProxyWidget
!), it gets selected. I click somewhere on the scene or another item - it gets deselected. I press the item and move around my cursor - both the item and the proxy attached to it as a child move around. Everything works as intended. -
With my custom
QGraphicsRectItem
whenever I hover my proxy, the parent (the custom item) changes its colour however if I hover the custom item nothing happening. Clicking onto the item doesn't work either. Clicking on the proxy works as expected - UI components (checkbox, combobox, button) can be interacted with.
I would very much like to have the same behaviour as in the linked answer of mine however with my custom
QGraphicsRectItem
(btw initially it was inheriting fromQGraphicsItem
but since a rectangle is what I need I decided to go for theQGraphicsRectItem
. Alas! neither worked as intended.After looking up online all I was able to find was the exact opposite - child item not handling events and not the parent. I have the feeling that for some reason my child (the proxy widget) is eating up my events that are supposed to be processed by the parent (the custom item). Then again this doesn't quite explain why an event on top of my proxy reflects in my custom item...
I am pretty sure that I haven't messed up the relation because
proxy->setParentItem(proxyControl); // proxy : QGraphicsProxyWidget | proxyControl : custom QGraphicsRectItem
and also calling
childItem()
on my custom item after adding the proxy widget as its childQList<QGraphicsItem *> childrenOfProxyControl = proxyControl->childItems(); for(int i = 0; i < childrenOfProxyControl.length(); i++) std::cout << "Child " << i << std::endl;
does return a single child for each node I add. If I call the code above on my proxy widget I don't get any children.
-
-
@Red-Baron
Hello,
I'm not completely sure I understand the problem, but it sounds like you want your parent's events to be processed in the child, which at least to my knowledge is quite the opposite of what Qt will do. If a child doesn't process (and accept) the event it'll be passed to the parent and so on, so it "bubbles up" the object hierarchy. I'm not aware, however, of any standard method to make the parent process its childrens' events besides intercepting them with an event filter. If you wish to experiment with that you could use QGraphicsItem::installSceneEventFilter.Kind regards.
-
@kshegunov Thanks for the reply.
No, I actually don't want parent to be processed in child. It's the current situation - child takes over the parent - that I have no idea how to fix. Since it's a proxy widget I don't want any
QGraphicsItem
specific mouse handlers so that I don't screw up the interaction with theQWdiget
that the proxy represents and its UI components (checkboxes, buttons etc.). As mentioned this unexpected behaviour started happening once I inheritedQGraphicsItem
(or its other flavours). It was not happening before when I was using a normalQGraphicsItem
. You can check what behaviour I really want to have by following the second link to SO - the answer I gave to a question there. The code can be compiled and executed. -
@Red-Baron
Do you mind putting a MWE in a repo somewhere, so I could just download the code an play with it a bit? It's quite hard to sift through all the fragments (and screenshots from the SO post). -
@kshegunov Sure thing. Thanks for taking the time. :) Here is the link to a GitLab repo with the code. It represents the very basics of what I'm going to do. I want to replace the default
QGraphicsItem
with my own adding hover event handling and other features. And here is the one with the customQGraphicsRectItem
that doesn't work as intended. -
@Red-Baron
Hello,
Change this:ProxyTransformItem::ProxyTransformItem(qreal x, qreal y, qreal _width, qreal _height, QGraphicsItem *parent) : QGraphicsRectItem(parent), w(_width), h(_height), hovered(false) { // ... }
To this:
ProxyTransformItem::ProxyTransformItem(qreal x, qreal y, qreal _width, qreal _height, QGraphicsItem *parent) : QGraphicsRectItem(QRectF(x, y, _width, _height), parent), w(_width), h(_height), hovered(false) { // ... }
And you'll see that you're going to correctly receive the events. If you need to know why, my guess is that the scene optimizes the items based on their geometry so you have a nice BSP retrieval (instead a full fledged searching) for an item (or a shape item that is) when the mouse moves. So making your rectangle item have invalid geometry in the constructor makes this impossible and consequently you're getting weird behavior. Additionally, if you're not doing anything special, you should use what the base class provides for the geometry instead of adding your own members (I'm talking about
qreal ProxyTransformItem::w
andqreal ProxyTransformItem::h
). Use QGraphicsRectItem::rect for that purpose, since you're already deriving from the rect item and probably something in the graphics scene engine already depends on itsrect
property. Furthermore, yourshape()
implementation seems to do what the default does - just returning the rectangle, so you should consider leaving the base class to handle that instead.PS.
Your problem didn't have to do with the proxy widget actually. While testing I removed it to verify that the rectangle item was receiving the events and it didn't. That's what made my investigations easier so I knew I had to focus on the rect item itself and not on the widget proxy.Kind regards.
-
@kshegunov Thanks for the corrections. The shape is something that I will be changing. As for the width and height - I changed that according to your suggestion.
Still things are not working properly. Only the first "node" (
QGraphicsRectItem
+QGraphicsProxyWidget
) can be moved, selected etc. The other two have the same behaviour as before. Also (and this applies for all) hovering the proxy widget still affects the parent instead of being ignored and only the parent having this ability.As for the problem being in the custom
QGraphicsRectItem
- yeap, I knew that since with the default implementation my proxy widget was working perfectly fine. :P -
@Red-Baron
It works for me (I'm testing on Qt 5.6) provided I fix up the geometry issue. Here's what I have as code (bear in mind I've moved everything to the stack where possible, not that it should matter in this case):QGraphicsProxyWidget * SceneWithMovableProxies::addWidgetToScene(QPointF initPos) { SceneWidgetItem * widget = new SceneWidgetItem(); qint32 width = widget->width(); ProxyTransformItem * proxyControl = new ProxyTransformItem(QRectF(initPos.x(), initPos.y(), width, 20)); proxyControl->setPaintTools(defaultPen, defaultInlineBrush, inlineBrushHover, inlineBrushSelected); QGraphicsProxyWidget * proxy = new QGraphicsProxyWidget(proxyControl); proxy->setWidget(widget); proxy->setPos(initPos.x(), initPos.y() + proxyControl->height()); proxy->setAcceptHoverEvents(false); ui.graphicsView->scene()->addItem(proxyControl); return proxy; }
And in the proxytransformitem.cpp I have:
ProxyTransformItem::ProxyTransformItem(const QRectF & rect, QGraphicsItem *parent) : QGraphicsRectItem(rect, parent), hovered(false) { setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); } float ProxyTransformItem::width() { return rect().width(); } float ProxyTransformItem::height() { return rect().height(); } QRectF ProxyTransformItem::boundingRect() const { return rect(); } QPainterPath ProxyTransformItem::shape() { QPainterPath path; path.addRect(rect()); return path; }
I believe that's all the changes I made. I hope it helps.
Kind regards.
-
@kshegunov Okay, I have forgotten to change my constructor facepalm. However it is still not working properly. :-/ I see you have set the
setAcceptHoverEvents()
of the proxy widget which seems to fix the problem and the proxy widget doesn't do anything whenever I hover it. Howeverproxy->setPos(initPos.x(), initPos.y() + proxyControl->height());
seems to be incorrect. If I add this the proxies are not glued to their parentQGraphicsRectItem
counterparts. Correct me if I'm wrong but I do believe that adding an item as a child to another item automatically "transfers" that child item to the local coordinate system of its parent. So if I useinitPos.x()
orinitPos.y() + proxyControl->height()
with thex
andy
different from 0 I will actually be translating my child item withx
andy
units inside the parent's coordinate system. At least that's how it appears on the screen. :D So I did// Just setting the rectangle for some reason doesn't seem to // do the trick; that's why previously I was manually setting // the position in my constructor by passing the x and y coordinates proxyControl->setPos(initPos); ... // 0 along the x and height() along the y position the proxy // widget right beneath my proxyControl as it's meant to be proxy->setPos(0, proxyControl->height());
In addition to that only the first node works properly. So not much has changed here. I even copy-pasted your code (of course
ui
in my case is not on the stack but as you've mentioned this shouldn't be the source of the problem at hand) but it's still broken.Even though Qt doesn't have backwards compatibility per se I doubt that things have changed that much between 5.5 and 5.6 in terms how the
QGraphicsRectItem
as well as theQGraphicsProxyWidget
behave. -
However it is still not working properly.
It's working on my machine (for 5.5 as well, I didn't have the Qt sources, that's why I was testing on 5.6). I've uploaded the whole project if you wish to
diff
and find out what are the differences exactly.Correct me if I'm wrong but I do believe that adding an item as a child to another item automatically "transfers" that child item to the local coordinate system of its parent. So if I use initPos.x() or initPos.y() + proxyControl->height() with the x and y different from 0 I will actually be translating my child item with x and y units inside the parent's coordinate system.
You're not wrong, however
proxyControl->pos()
returns (0, 0) for each instance for me. What I assume is happening is that the items are not immediately "moved" to their positions, so if I don't specify the global coordinates the proxies are not positioned properly.Even though Qt doesn't have backwards compatibility per se
Actually it does, for the interfaces at least. All minor versions are binary compatible, so the interfaces don't change (this includes 5.5 and 5.6).
-
@kshegunov Your code is working so I can do some comparison and dig into it to see where the differences are. Thank you so much for helping me with this! Much appreciated. ^_^