Qt的QGraphicsItem中的事件和信号:这个*应该怎么工作?

时间:2023-02-02 00:11:17

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:

与Qt中的其他原语一样,QGraphicsItems可以处理鼠标事件等。甜蜜的!现在假设我需要将一个QGraphicsItem上的事件传播到同一场景中的其他QGraphicsItem。我可以想出两种方法来解决这个问题:


(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.

概念:连接兄弟姐妹QGraphicsItems与信号。QGraphicsItem调用emit()上的事件处理程序,它在其他QGraphicItems上引发协调的响应。这遵循了在整个Qt框架中建立的通用设计模式。

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.

实现:由于我没有完全掌握的原因,QGraphicsItems不能发射()信号。有人建议,同样继承自QGraphicsObject的派生类可以处理这个问题。但是,在我看来,QGraphicsItems中排除emit()可能是Qt开发人员有意设计的决定,因此,多重继承可能不是正确的解决方案。

(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.

概念:QGraphicsItems总是存在于QGraphicsScene类型的容器的上下文中。(A)在QGraphicsItem级别上处理的事件由一个从QGraphicsScene继承的对象处理。该对象还实现了协调同级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.

实现:QGraphicsScene绝对有能力处理事件,否则这些事件会一直到QGraphicsItems。QGraphicsScene还提供了itemsAt()方法,用于确定其中哪些内容受到位置事件(如鼠标单击)的影响。尽管如此,在容器类中为容器之间的协调操作构建相当大的逻辑,感觉像是未能正确封装。糟糕的实践?也许吧,但至少在一个官方案例中是这样做的。


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. 正确的解是什么?如果不是A或B,那是不是还有什么我没有想到的?
  3. 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.
  4. 为什么Qt开发人员允许QGraphicsItems接收事件而不发送信号?这似乎是整个框架中使用的设计模式的一个主要例外。
  5. 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?
  6. 这个问题的扩展是QGraphicsItems和高阶容器类(如主应用程序)之间的通信。这是怎么说的呢?

2 个解决方案

#1


7  

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.

信令不是QGraphicItem的一部分,因为它们不从QObjects继承。这是出于性能考虑的设计决策,允许非常大和快速的场景。如果您决定确实需要特殊的信号用例,则创建QGraphicsWidget来填补这个空白。它确实继承了QObject,并允许您混合使用QWidget和QGraphicsItem功能。尽管建议您避免这种情况,如果您的场景甚至是适度的大小。

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.

另一个可能与您的情况相关的选项是使用sceneEventFilter方法。您可以设置一个项为另一个项接收事件,并决定它们是否应该传播:http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qgraphicsitem.html#sceneEventFilter一个项可以设置为多个对象的过滤器。它可以识别每个单独的项目和事件来响应。

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

而且,似乎您的选项A是不可能的,因为QGraphicsItem甚至没有一个emit方法。您需要将QObject实例作为成员组合在其中,并使用它发出信号。一些沿着myItem.qobject.emit()的代码。否则,您将不得不从QGraphicsObject继承您自己的完全自定义的一个

Update 1: Addressing your main comment update

更新1:处理你的主要评论更新

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.

您的具体情况是一个带有“热角”的矩形。我将看到这是一个定制的QGraphicsItem。您可能会子类化QGraphicsRectItem,然后将子热角项组合为子项(setparentiem())。现在,您的矩形项知道它的子对象,并可以直接对它们进行操作。您可以将矩形项设置为孩子的sceneEventFilter并直接处理他们的事件。没必要再回到现场。让这些逻辑在课堂上发挥作用。

Update 2: Addressing your added question #3

更新2:解决问题3

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

将通信从现场传播到QWidget有几个方法:

  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. 如果您希望使用QGraphicsObject的子类作为根项,然后将其余的对象组合为子对象(rect,然后是rect的子对象),那么您可以考虑这种情况。这将允许对象发出信号。为了清晰起见,他们可能还会连接到场景,然后更高阶的场景容器会连接到场景。根据场景的复杂性以及QGraphicsObject是否对性能有影响,您必须根据具体情况选择这种方法。如果您有很多这样的实例,您可能应该避免这种情况。
  3. 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.
  4. 您可以为您的rect类定义一个回调,这个场景可以为此设置。将回调调整为属性或setter graphicsRect.setResizedCallback(cbk)。在rect类中,只要在合适的时候调用即可。如果它设置了回调,它可以用来直接调用场景中的某些东西。rect类仍然不了解这种逻辑。它只调用一个回调。

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

这些只是一些建议。我肯定还有别的办法。

#2


1  

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.

我建议B,除非你的qgraphicsitem相对较少。我认为qgraphicsitem不是qobject,因为QObjects有一定的开销。QGraphicsView框架的设计目的是允许在场景中快速插入和删除许多(例如,数千)QGraphicsItems,因此最好采用重量更轻的方法。

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:

我将在QGraphicsItems中寻找育儿的概念。QGraphicsItems可以有父母和孩子,这与QObjects之间的养育相似。例如,如果移动父类QGraphicsItem,它的子类就会随之移动,如果删除父类,它的子类就会被删除。您可以使用QGraphicsItem:: parityem()和使用QGraphicsItem:: childitem()的孩子访问QGraphicsItem的父类。因此,您可以很容易地访问这样的兄弟类:

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

Note that mySiblings includes this.

请注意,mybrothers包括这个。

#1


7  

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.

信令不是QGraphicItem的一部分,因为它们不从QObjects继承。这是出于性能考虑的设计决策,允许非常大和快速的场景。如果您决定确实需要特殊的信号用例,则创建QGraphicsWidget来填补这个空白。它确实继承了QObject,并允许您混合使用QWidget和QGraphicsItem功能。尽管建议您避免这种情况,如果您的场景甚至是适度的大小。

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.

另一个可能与您的情况相关的选项是使用sceneEventFilter方法。您可以设置一个项为另一个项接收事件,并决定它们是否应该传播:http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qgraphicsitem.html#sceneEventFilter一个项可以设置为多个对象的过滤器。它可以识别每个单独的项目和事件来响应。

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

而且,似乎您的选项A是不可能的,因为QGraphicsItem甚至没有一个emit方法。您需要将QObject实例作为成员组合在其中,并使用它发出信号。一些沿着myItem.qobject.emit()的代码。否则,您将不得不从QGraphicsObject继承您自己的完全自定义的一个

Update 1: Addressing your main comment update

更新1:处理你的主要评论更新

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.

您的具体情况是一个带有“热角”的矩形。我将看到这是一个定制的QGraphicsItem。您可能会子类化QGraphicsRectItem,然后将子热角项组合为子项(setparentiem())。现在,您的矩形项知道它的子对象,并可以直接对它们进行操作。您可以将矩形项设置为孩子的sceneEventFilter并直接处理他们的事件。没必要再回到现场。让这些逻辑在课堂上发挥作用。

Update 2: Addressing your added question #3

更新2:解决问题3

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

将通信从现场传播到QWidget有几个方法:

  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. 如果您希望使用QGraphicsObject的子类作为根项,然后将其余的对象组合为子对象(rect,然后是rect的子对象),那么您可以考虑这种情况。这将允许对象发出信号。为了清晰起见,他们可能还会连接到场景,然后更高阶的场景容器会连接到场景。根据场景的复杂性以及QGraphicsObject是否对性能有影响,您必须根据具体情况选择这种方法。如果您有很多这样的实例,您可能应该避免这种情况。
  3. 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.
  4. 您可以为您的rect类定义一个回调,这个场景可以为此设置。将回调调整为属性或setter graphicsRect.setResizedCallback(cbk)。在rect类中,只要在合适的时候调用即可。如果它设置了回调,它可以用来直接调用场景中的某些东西。rect类仍然不了解这种逻辑。它只调用一个回调。

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

这些只是一些建议。我肯定还有别的办法。

#2


1  

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.

我建议B,除非你的qgraphicsitem相对较少。我认为qgraphicsitem不是qobject,因为QObjects有一定的开销。QGraphicsView框架的设计目的是允许在场景中快速插入和删除许多(例如,数千)QGraphicsItems,因此最好采用重量更轻的方法。

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:

我将在QGraphicsItems中寻找育儿的概念。QGraphicsItems可以有父母和孩子,这与QObjects之间的养育相似。例如,如果移动父类QGraphicsItem,它的子类就会随之移动,如果删除父类,它的子类就会被删除。您可以使用QGraphicsItem:: parityem()和使用QGraphicsItem:: childitem()的孩子访问QGraphicsItem的父类。因此,您可以很容易地访问这样的兄弟类:

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

Note that mySiblings includes this.

请注意,mybrothers包括这个。