Custom QGraphicsEllipseItem mousePressedEvent single clicks
-
Hello,
I have a custom QGraphicsEllipseItem to represent a node in a graph with the following code (everything is inlined for testing purposes).
constexpr int const R = 50; class StateWidget : public QGraphicsEllipseItem { public: float x, y; inline StateWidget(QPointF p) : StateWidget(p.x(), p.y()) {} inline StateWidget(float x, float y, QGraphicsItem* parent = nullptr): QGraphicsEllipseItem(x,y,R,R) { setBrush(QBrush(Qt::white)); setZValue(10); } protected: inline void mousePressEvent(QGraphicsSceneMouseEvent *event) override { event->accept(); std::cout << "State pressed\n"; } };
These states widgets are then added to a custom GraphicsView through its GraphicsScene. The GraphicsView mouse event code is as such
void GraphicView::mousePressEvent(QMouseEvent* event) { if(itemAt(event->pos()) != nullptr) return; std::cout << "GV clicked\n"; }
When I click into an empty space in the Graphics Scene it does print "GV clicked" correctly. However, I need to double click the ellipse for the StateWidget event to trigger. The documentation mentions that for a QGraphicsItem if the
mousePressEvent
is overriden then the item will accept move, release and double click events. What I want though is to trigger the event with a single click as I want to be able to select the state. I also don't understand why themousePressEvent
would specifically accept double clicks whenmouseDoubleClickEvent
exists. -
@Deneguil said in Custom QGraphicsEllipseItem mousePressedEvent single clicks:
I also don't understand why the mousePressEvent would specifically accept double clicks when mouseDoubleClickEvent exists.
I don't understand the problem.
A double click consists of press, release, press, release in a given time period.
So why would clicking twice very fast not trigger single events?! -
@Deneguil
You need to understand how event propagate.- The event is first detected by your OS/Platform. If the area where it happened "belongs" to your application, it will be forwarded to your application's event loop (which is handled by Qt)
- Qt forwards it to a global event handler function (which you could override, but which I assume you have not changed)
- When using the default global event handler, any globally installed event filter will be called and may block the event. This is probably also not the case
- A use input event is then forwarded to the correct QWidget (in a QWidget application such as this one). That's the topmost widget covering the area of the event (or the event grabber widget, which is mostly important in case of drag-drop operations)
- QGraphicsView is a widget, so it receives the event. You receive the event and then block it from further propagation by not passing it to your base class. Your journey ends here.
- The QQGraphicsView base class would forward the event to the QGraphicsScene. You can override events there, too
- Unless the event is blocked, the QGraphicsScene will detect the topmost QGraphicsItem under the event position, and deliver the event there
- If the event is not blocked and not accepted, it will propagate to all QGraphicsItems below the event position, until either one of them accepts it, or we run out of items to try.
So, this line of code causes the behavior you observe:
if(itemAt(event->pos()) != nullptr) return;
-
Aren't events propagated bottom up (innermost child = bottom)?!
You don't call the base(!) implementation in event handlers for nothing ;-)In situations like you describe, the event comes from the top most parent and is propagated down.
If I'm not mistaken exactly the opposite is the case.
If child don't accept the event, the parent gets it... if parent don't want the event, its grandparent gets it... until the event is accepted or "eaten" so that the propagation stops -
Thank you very much for your in depth breakdown of the event propagation pipeline.
I originally wrote the following line to prevent both events from firing.
if(itemAt(event->pos()) != nullptr) return;
Without it it'd print both "GV clicked" and "State clicked".
If modified it to this one now and it works perfectly.
if(itemAt(event->pos()) != nullptr) { QGraphics::mousePressEvent(event); return; }
Both strings are still printed without the
return
which is why it's still here for apart from that it's perfect.Thank you!
-
-
I am calling
accept()
in the state event handler but it still prints both strings.The application is a GUI automata editor so the idea is to have a Graphics View displaying the graph and the user would have the ability to click on a node or edge and it'd display its parameters in a toolbox. So I want to isolate the click event of the Graphics Items from the one of the View as much as possible.
The
return
might not be the cleanest way to solve this small issue but it works well enough for the time being. And seeing how there'll be several modes in the application to either add a new state, transition or simply to select one, I should be able to use those to check whether the event is valid or not too. -
@Pl45m4 said in Custom QGraphicsEllipseItem mousePressedEvent single clicks:
Aren't events propagated bottom up (innermost child = bottom)?!
You don't call the base(!) implementation in event handlers for nothing ;-)In situations like you describe, the event comes from the top most parent and is propagated down.
If I'm not mistaken exactly the opposite is the case.
If child don't accept the event, the parent gets it... if parent don't want the event, its grandparent gets it... until the event is accepted or "eaten" so that the propagation stopsYou are thinking of QWidget.
In QGraphicsView, things are more complex:- Parent can be on top of children thanks to QGraphicsItem::ItemStacksBehindParent or a combination of QGraphicsItem::ItemNegativeZStacksBehindParent and negative Z-Order
- Children can be outside the shape of a parent, unless the parent has QGraphicsItem::ItemClipsChildrenToShape set
For those reasons, the QGraphicsItem cannot possibly know whom to forward the event to (see also the implementation of QGraphicsItem::mousePressEvent in qgraphicsitem.cpp).
The reason you should still forward the event to base class is that the item does a few item-related things, like moving items that are moveable.
-
@Deneguil said in Custom QGraphicsEllipseItem mousePressedEvent single clicks:
Thank you very much for your in depth breakdown of the event propagation pipeline.
I originally wrote the following line to prevent both events from firing.
if(itemAt(event->pos()) != nullptr) return;
Without it it'd print both "GV clicked" and "State clicked".
If modified it to this one now and it works perfectly.
if(itemAt(event->pos()) != nullptr) { QGraphics::mousePressEvent(event); return; }
Both strings are still printed without the
return
which is why it's still here for apart from that it's perfect.Thank you!
However, this way you are bypassing a lot of things, which could lead to confusion the next time you'd like to add something.
What you probably want (I'm guessing a bit here) is this:
void GraphicView::mousePressEvent(QMouseEvent* event) { QGraphicsView::mousePressEvent(event); if(event->isAccepted()) return; std::cout << "GV clicked\n"; }
Now you can catch the event in the item, handle and accept it.
The view will know that someone has taken care of the event, and it doesn't need to consider it.