Qt的QGraphicsItem中的事件和信号:这应该如何工作?

13

和Qt中的其他原语一样,QGraphicsItems可以处理鼠标事件等。太好了!现在假设我需要将一个QGraphicsItem上的事件传播到同一场景中的其他QGraphicsItems上。我可以想到两种方法:


(A) 愚蠢的方法 - 信号

概念:使用信号将兄弟QGraphicsItems连接在一起。QGraphicsItem上的事件处理程序调用emit(),引发其他QGraphicItems的协调响应。这遵循Qt框架中建立的一般设计模式。

实现:由于某些原因,QGraphicsItems无法发出信号。有人建议从QGraphicsObject继承的派生类可能能够解决此问题。然而,对我来说,排除在QGraphicsItems上使用emit()可能是Qt开发人员的一个有意设计决策,因此多重继承可能不是正确的解决方案。

(B) 容器级事件处理

概念:QGraphicsItems始终存在于类型为QGraphicsScene的容器中。在(A)中处理的事件现在由继承自QGraphicsScene的对象处理。该对象还实现了协调兄弟QGraphicsItems之间响应的逻辑。

实现:QGraphicsScene绝对有处理事件的能力,否则这些事件会传递到QGraphicsItems。QGraphicsScene还提供了itemsAt()方法,用于确定哪些内容受到位置事件(如鼠标单击)的影响。然而,在容器类中构建大量逻辑以协调包含物之间的操作感觉像是未能正确封装。不好的做法?也许是的,但至少在一个官方示例中是这样做的。


问题

  1. 这里的正确解决方案是什么?如果不是 A 或 B,那么还有其他我没有想到的吗?
  2. 为什么 Qt 开发人员允许 QGraphicsItems 接收事件但不发送信号?这似乎是整个框架中使用的设计模式的一个重要例外。
  3. 这个问题的扩展是 QGraphicsItems 和更高级别的容器类(如主应用程序)之间的通信。这该如何解决?

请注意:这里的使用案例是一个可调整大小的矩形。它的每个角落都有作用点,当点击并拖动时,会导致相应的边缘也移动。 - BrianTheLion
这些边缘不应该是矩形的子元素吗?因此父元素应该知道它们吧? - jdi
我猜是的,如果我使用QGraphicsItemGroup或类似的东西来实现(B)。 - BrianTheLion
不是,我的意思是这种情况听起来你应该创建一个具有子项的自定义子类。因此,父级可以直接对子级进行操作,而不是协调上下。 - jdi
2个回答

9

QGraphicItem没有包含信号,因为它们不继承自QObjects。这是出于性能原因的设计决策,以允许非常大且快速的场景。如果您确实需要特殊情况的信号,QGraphicsWidget被创建来填补这个空白。它确实继承自QObject,并允许您具有QWidget和QGraphicsItem功能的混合体。但是,如果您的场景即使是适度大小,建议避免使用此选项。

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

通常,您应该利用场景协调其对象之间的协作。这已经是事件使用的模式(场景协调向所有项目交付事件)。

另外,似乎您的选项A不可行,因为QGraphicsItem甚至没有emit方法。您需要在其中组合一个QObject实例作为成员,并使用它来发出信号。类似于myItem.qobject.emit()。否则,您将不得不从QGraphicsObject继承自己完全定制的内容。 更新1:回应您的主要评论更新
您的具体情况是带有“热角”的矩形。我认为这将是一个自定义的QGraphicsItem。您可能会子类化QGraphicsRectItem,然后在内部作为子项组成子热角项(setParentItem())。现在,您的矩形项知道其子项并可以直接对其进行操作。您可以将矩形项设置为子项的sceneEventFilter并直接处理它们的事件。无需返回到场景。让所有这些逻辑都存在于类中。 更新2:回应您添加的问题#3
将通信传播到QWidget之上超出了场景的范围,我可以想到几种方法:
  1. 如果您考虑使用QGraphicsObject子类作为根项目,并将其余的对象组合为子项(矩形,然后是矩形的热点作为其子项),则可以考虑这种情况。这将允许对象发出信号。为了清晰起见,它们可能仍然连接到场景,然后场景的高级容器将连接到场景。您必须根据情况选择此方法,具体取决于场景的复杂性以及QGraphicsObject对其性能是否有影响。如果您将拥有大量这些实例,则应避免使用此方法。
  2. 您可以为矩形类定义回调函数,场景可以设置该回调函数。可以像这样设置:graphicsRect.resizedCallback 作为属性,或者使用setter graphicsRect.setResizedCallback(cbk)。在矩形类中,您只需在适当的时候调用它。如果设置了回调函数,则可以直接在场景上调用它。矩形类仍然不知道该逻辑。它只是调用回调函数。

这些只是一些建议。我相信还有其他方式。


@BrianTheLion:是的,那就是我所指的针对你的情况所要做的。它使用事件过滤器。 - jdi
@jdl:干杯!你对我的第三个问题有什么想法吗?这是一个晚期添加的问题。在可调整大小的矩形的情况下,每次矩形坐标更改时,我需要调整持久存储中的数据结构。 - BrianTheLion

1

如果您的QGraphicsItems数量相对较少,我建议使用B。我认为QGraphicsItems不是QObjects,因为QObjects会带来一定的开销。QGraphicsView框架旨在允许快速将许多(例如数千个)QGraphicsItems插入和删除到场景中,因此更轻量级的方法更受欢迎。

我建议您查看QGraphicsItems中的父子关系概念。QGraphicsItems可以有父项和子项,并且这与QObjects之间的父子关系具有类似的效果。例如,如果您移动一个父QGraphicsItem,则其子项也会随之移动,如果您删除一个父项,则其子项也将被删除。您可以使用QGraphicsItem::parentItem()访问QGraphicsItem的父项,使用QGraphicsItem::childItems()访问其子项。因此,您可以轻松地访问兄弟项,如下所示:

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

请注意,mySiblings 包括 this。这是与IT相关的内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接