Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Events and signals in Qt's QGraphicsItem: How is this *supposed* to work?

Tags:

qt

qt4

pyqt4

Like other primitives in Qt, QGraphicsItems can handle mouse events and the like. Sweet! Now say I need for an event on one QGraphicsItem to be propagated to some other QGraphicsItems in the same scene. I can think of two ways that one might approach this:


(A) The Naive Approach - Signaling

Concept : Connect sibling QGraphicsItems together with signals. Event handlers on QGraphicsItem call emit()s that evoke coordinated responses on other QGraphicItems. This follows the general design pattern established throughout the Qt framework.

Implementation : For reasons that I do not fully grasp, QGraphicsItems cannot emit() signals. It has been suggested that derived classes that also inherit from QGraphicsObject may be able to work around this. It seems to me, though, that the exclusion of emit() on QGraphicsItems was probably an intentional design decision on the part of the Qt devs and, therefore, multiple inheritance is probably not the Right Solution.

(B) Container-Level Event Handling

Concept : QGraphicsItems always exist in the context of a container of type QGraphicsScene. Events that in (A) were handled at the level of the QGraphicsItem are instead handled by an object inheriting from QGraphicsScene. This object also implements the logic for coordinating responses between sibling QGraphicsItems.

Implementation : QGraphicsScene definitely has the ability to handle events that would otherwise make their way down to QGraphicsItems. QGraphicsScene also provides the itemsAt() method for determining which of the things in it are affected by positional events, like mouse clicks. Still, building up considerable logic within a container class for coordinated action among containees feels like a failure to encapsulate properly. Bad practice? Maybe, but this seems to be the way it's done in at least one official example.


Questions

  1. What's the Right Solution here? If not A or B, then is it something else that I haven't thought of?
  2. Why did the Qt devs allow QGraphicsItems to receive events but not send signals? This seems like a major exception to the design pattern used throughout the framework.
  3. An extension of this problem is communication between QGraphicsItems and higher-order container classes, like the main application. How is that meant to be addressed?
like image 896
BrianTheLion Avatar asked May 14 '12 20:05

BrianTheLion


People also ask

What is qgraphicsitem in Qt?

This includes defining the item's geometry, collision detection, its painting implementation and item interaction through its event handlers. QGraphicsItem is part of the Graphics View Framework For convenience, Qt provides a set of standard graphics items for the most common shapes. These are:

How does qgraphicsitem receive events from qgraphicsscene?

QGraphicsItem receives events from QGraphicsScene through the virtual function sceneEvent (). This function distributes the most common events to a set of convenience event handlers: You can filter events for any other item by installing event filters.

What are signals and slots in Qt?

Signals and slots are made possible by Qt's meta-object system. In GUI programming, when we change one widget, we often want another widget to be notified. More generally, we want objects of any kind to be able to communicate with one another.

What is the use of focusoutevent () function in qgraphicsitem?

See also QGraphicsItem::focusOutEvent (). When the scene is active, this functions returns the scene's current focus item, or nullptr if no item currently has focus. When the scene is inactive, this functions returns the item that will gain input focus when the scene becomes active.


2 Answers

Signaling is not part of QGraphicItem because they do not inherit from QObjects. This was a design decision for performance reasons, to allow very large and fast scenes. If you decide you really need special cases for signals, QGraphicsWidget was created to fill this gap. It does inherit from QObject and lets you have a mixture of QWidget and QGraphicsItem functionality. Though it is recommended that you avoid this if your scenes are even moderately sizable.

Another option which might be relevant to your situation is to make use of the sceneEventFilter method. You can set one item to receive events for another and decide if they should be propagated or not: http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qgraphicsitem.html#sceneEventFilter
One item can be set as the filter for multiple objects. And it can identify each individual item and event to respond to.

Generally though you should make use of the scene for coordination across its objects. That is already the pattern being used for events (the scene coordinating delivery of all events to the items).

Also, it seems that your option A is not possible because QGraphicsItem does not even have an emit method. You would need to compose a QObject instance inside it as a member and use that to emit signals. Something along the lines of myItem.qobject.emit(). Otherwise, you would have to inherit your own completely custom one from QGraphicsObject

Update 1: Addressing your main comment update

Your specific situation is a rectangle with "hot corners". I would see this being a custom QGraphicsItem. You would probably subclass QGraphicsRectItem, and then compose the child hot corner items inside as children items (setParentItem()). Now your rectangle item knows about its children and can act on them directly. You could set the rectangle item to be the sceneEventFilter for the children and handle their events directly. No need to go back up to the scene. Let all this logic live in the class.

Update 2: Addressing your added question #3

Propagating communications up beyond the scene to QWidget's has a couple approaches I can think of:

  1. This is a situation where you can consider if you want to use a QGraphicsObject subclass as your root item, and then compose the rest of your objects as children (the rect, then the hot corners as children of the rect). This would allow the object to emit signals. For clarity they would probably still be connected to the scene, and then the higher order container of the scene would connect to the scene. You would have to choose this approach on a case by case, depending on the complexity of your scene and whether the QGraphicsObject has any performance impact on it. You should probably avoid this if you will have a great number of these instances.
  2. You could define a callback for your rect class, for which the scene can set. Either something like: graphicsRect.resizedCallback as an attribute, or a setter graphicsRect.setResizedCallback(cbk). In your rect class, you would just call that when appropriate. If the callback it set, it can be used to call something on your scene directly. The rect class still has no knowledge of that logic. It just calls a callback.

Those are just some suggestions. I'm sure there are other ways.

like image 196
jdi Avatar answered Oct 18 '22 18:10

jdi


I suggest B unless you have relatively few QGraphicsItems. I believe QGraphicsItems are not QObjects because there is a certain amount of overhead associated with QObjects. The QGraphicsView framework was designed to allow fast insertion and deletion of many (e.g., thousands) QGraphicsItems into a scene, so a lighter-weight approach was preferred.

I would look to the concept of parenting in QGraphicsItems. QGraphicsItems can have parents and children, and this has several effects similar to parenting amongst QObjects. For example, if you move a parent QGraphicsItem, its children will move with it, and if you delete a parent, its children will be deleted. You can access a QGraphicsItem's parent using QGraphicsItem::parentItem(), and children using QGraphicsItem::childItems(). So, you can easily access sibling items like this:

QList<QGraphicsItem *> mySiblings = this->parentItem()->childItems();

Note that mySiblings includes this.

like image 30
Anthony Avatar answered Oct 18 '22 16:10

Anthony