QGraphicsItem 在更改 child 时不更改 parent 的笔

QGraphicsItem don't change pen of parent when chaning child

我有多个 QGraphicsItem 位于 parent-child 层次结构中。我正在尝试在鼠标悬停时突出显示一个项目以在项目基础上工作,这意味着如果鼠标悬停在一个项目上它应该突出显示。

突出显示效果很好,但如果我在 child 上执行突出显示,那么突出显示也会自动发生在 parent 上,这是不希望的。这是问题的代码示例

from PySide2.QtCore import Qt
from PySide2.QtGui import QPen
from PySide2.QtWidgets import QGraphicsItem, \
    QGraphicsScene, QGraphicsView, QGraphicsLineItem, QGraphicsSceneHoverEvent, \
    QGraphicsRectItem, QMainWindow, QApplication

class TextBox(QGraphicsRectItem):
    def __init__(self, parent: QGraphicsItem, x: float, y: float, width: float, height: float):
        super().__init__(parent)
        self.setParentItem(parent)

        self.setAcceptHoverEvents(True)

        pen = QPen(
            Qt.white,
            2,
            Qt.SolidLine,
            Qt.RoundCap,
            Qt.RoundJoin
        )

        self.setPen(pen)
        self.setRect(x, y, width, height)

    def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
        super().hoverEnterEvent(event)

        current_pen = self.pen()
        current_pen.setWidth(5)
        self.setPen(current_pen)

    def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
        super().hoverLeaveEvent(event)

        current_pen = self.pen()
        current_pen.setWidth(2)
        self.setPen(current_pen)


class LineItem(QGraphicsLineItem):
    def __init__(
            self,
            x1_pos: float,
            x2_pos: float,
            y1_pos: float,
            y2_pos: float,
            parent: QGraphicsItem = None
    ):
        super().__init__()
        self.setParentItem(parent)

        self.setAcceptHoverEvents(True)

        pen = QPen(
            Qt.white,
            2,
            Qt.SolidLine,
            Qt.RoundCap,
            Qt.RoundJoin
        )

        self.setPen(pen)

        self.setLine(
            x1_pos,
            y1_pos,
            x2_pos,
            y2_pos
        )

    def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
        super().hoverEnterEvent(event)

        current_pen = self.pen()
        current_pen.setWidth(5)
        self.setPen(current_pen)

    def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
        super().hoverLeaveEvent(event)

        current_pen = self.pen()
        current_pen.setWidth(2)
        self.setPen(current_pen)


class DiagramScene(QGraphicsScene):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setBackgroundBrush(Qt.black)

        line_item = LineItem(0, 200, 0, 0)
        box = TextBox(line_item, 0, 0, 20, 20)

        self.addItem(line_item)


class GraphicsView(QGraphicsView):
    def __init__(self):
        self.scene = DiagramScene()
        super().__init__(self.scene)


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setCentralWidget(GraphicsView())


if __name__ == '__main__':

    import sys

    app = QApplication(sys.argv)

    mainWindow = MainWindow()
    mainWindow.setGeometry(100, 100, 800, 500)
    mainWindow.show()

    sys.exit(app.exec_())

将鼠标悬停在该行(即 parent)上时,只会突出显示该行。但是,当将鼠标悬停在矩形上时,两者都会突出显示。我认为这与矩形是订单项的 child 有关。

我想保留 parent-child 层次结构,因为我必须根据 parent 位置计算 child 位置,这样更容易。

有没有办法不将 child 项目的突出显示级联到 parent 为?

一种可能的解决方案是检查事件位置是否确实在项目的 boundingRect() 内,但这仅适用于仅垂直和水平延伸的项目。由于线条也可以有一定的角度,因此最好检查 shape() 而不是:

def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
    super().hoverEnterEvent(event)

    if self.shape().contains(event.pos()):
        current_pen = self.pen()
        current_pen.setWidth(5)
        self.setPen(current_pen)

不过,“离开”部分有点棘手:parent 悬停在 child 上时不会收到离开事件,它需要添加 所有 children.

上的场景 事件过滤器

由于场景事件过滤器只能在项目出现在场景中时安装,因此您必须在添加 child 或 时尝试安装过滤器当 parent 添加到场景时。

为了简单起见,我创建了两个不同宽度的类似笔,这样你只需要使用setPen()而不是不断地获取当前的笔并更改它。

class LineItem(QGraphicsLineItem):
    def __init__(
            self,
            x1_pos: float,
            x2_pos: float,
            y1_pos: float,
            y2_pos: float,
            parent: QGraphicsItem = None
    ):
        super().__init__()
        self.setParentItem(parent)

        self.setAcceptHoverEvents(True)

        self.normalPen = QPen(
            Qt.green,
            2,
            Qt.SolidLine,
            Qt.RoundCap,
            Qt.RoundJoin
        )

        self.hoverPen = QPen(self.normalPen)
        self.hoverPen.setWidth(5)

        self.setPen(self.normalPen)

        self.setLine(
            x1_pos,
            y1_pos,
            x2_pos,
            y2_pos
        )

    def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
        super().hoverEnterEvent(event)
        if self.shape().contains(event.pos()):
            self.setPen(self.hoverPen)

    def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
        super().hoverLeaveEvent(event)
        self.setPen(self.normalPen)

    def itemChange(self, change, value):
        if change == self.ItemChildAddedChange and self.scene():
            value.installSceneEventFilter(self)
        elif change == self.ItemChildRemovedChange:
            value.removeSceneEventFilter(self)
        elif change == self.ItemSceneHasChanged and self.scene():
            for child in self.childItems():
                child.installSceneEventFilter(self)
        return super().itemChange(change, value)

    def sceneEventFilter(self, child, event):
        if event.type() == event.GraphicsSceneHoverEnter:
            self.setPen(self.normalPen)
        elif (event.type() == event.GraphicsSceneHoverLeave and 
            self.shape().contains(child.mapToParent(event.pos()))):
                self.setPen(self.hoverPen)
        return super().sceneEventFilter(child, event)