You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt6中QGraphicsItem事件处理问题:不可见项的悬停与鼠标事件

PyQt6 QGraphicsItem事件处理:不可见项接收hover事件+鼠标事件响应问题

问题概述

  • 场景包含两类元素:QGraphicsPixmapItem实现的背景图、自定义多边形QGraphicsItem
  • 核心需求:
    1. 可见多边形:鼠标悬停时高亮,离开时恢复正常状态
    2. 不可见多边形:鼠标悬停时高亮显示,离开时恢复不可见状态
  • 遇到的问题:
    • 不可见/禁用的Item无法接收hover事件
    • 可见多边形也无法响应鼠标移动、释放等事件
    • 希望了解QEvent的通用传播机制

原测试代码

from PyQt6.QtCore import Qt, QRectF, QSize, QPointF
from PyQt6.QtGui import QImage, QPixmap, QColor, QPen, QBrush, QPainter, QPainterPath, QPolygonF
from PyQt6.QtWidgets import QGraphicsView, QGraphicsScene, QApplication, QGraphicsItem, QGraphicsPixmapItem


class QGraphicsCustomPolygonItem(QGraphicsItem):
    def __init__(self, polygon, default_visibility):
        super().__init__()

        self.default_visibility = default_visibility
        self.polygon = polygon

        self.main_color = QColor(255, 0, 0)

        self.pen_color = self.main_color
        self.pen_color.setAlpha(255)
        self.pen = QPen(self.pen_color)
        self.pen.setWidthF(0.5)

        self.brush_color = self.main_color
        self.brush_color.setAlpha(32)
        self.brush = QBrush(self.brush_color)

        self._highlight = False

        self.setVisible(default_visibility)
        self.setAcceptHoverEvents(True)

    @property
    def highlight(self) -> bool:
        return self._highlight

    @highlight.setter
    def highlight(self, value: bool) -> None:
        assert isinstance(value, bool)
        self._highlight = value
        self.brush_color = self.main_color
        if value is True:
            self.brush_color.setAlpha(64)
            self.setVisible(True)
        else:
            self.brush_color.setAlpha(32)
            self.setVisible(self.default_visibility)
        self.brush = QBrush(self.brush_color)
        self.update(self.boundingRect())

    def boundingRect(self) -> QRectF:
        return self.polygon.boundingRect()

    def paint(self, painter: QPainter, option, widget=None):
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(self.polygon, Qt.FillRule.OddEvenFill)

    def shape(self):
        path = QPainterPath()
        path.addPolygon(self.polygon)
        return path

    def mouseMoveEvent(self, event):
        print("mouse move")

    def mousePressEvent(self, event):
        print("mouse press")

    def mouseReleaseEvent(self, event):
        print("mouse release")

    def mouseDoubleClickEvent(self, event):
        print("mouse double click")

    def hoverMoveEvent(self, event):
        print("hover move")

    def hoverEnterEvent(self, event):
        print("hover enter")
        self.highlight = True

    def hoverLeaveEvent(self, event):
        print("hover leave")
        self.highlight = False


if __name__ == '__main__':
    app = QApplication([])

    scene = QGraphicsScene()

    # 绘制背景图
    background_image = QImage(QSize(300, 300), QImage.Format.Format_RGB888)
    for x in range(background_image.width()):
        for y in range(background_image.height()):
            if (x + y) % 2 == 0:
                background_image.setPixelColor(x, y, Qt.GlobalColor.white)
            else:
                background_image.setPixelColor(x, y, Qt.GlobalColor.black)
    background_item = QGraphicsPixmapItem(QPixmap.fromImage(background_image))
    scene.addItem(background_item)

    # 绘制自定义多边形
    poly = QPolygonF([QPointF(x, y) for x, y in [(20, 40), (200, 60), (120, 250), (50, 200)]])
    custom_polygon_item = QGraphicsCustomPolygonItem(poly, default_visibility=True)
    scene.addItem(custom_polygon_item)

    # 初始化视图
    view = QGraphicsView(scene)
    view.setGeometry(0, 0, 600, 600)
    view.show()
    app.exec()

解决方案

1. 让不可见Item接收hover事件

核心思路:不要用setVisible()控制Item的显示状态,而是保持Item始终可见(setVisible(True)),通过paint()函数控制是否绘制内容。这样Scene会将Item纳入事件分发列表,hover事件能正常传递。

修改后的关键代码:

class QGraphicsCustomPolygonItem(QGraphicsItem):
    def __init__(self, polygon, default_visibility):
        super().__init__()
        # ... 其他初始化代码 ...
        # 保持Item始终可见,确保事件能被接收
        self.setVisible(True)
        self.setAcceptHoverEvents(True)

    @highlight.setter
    def highlight(self, value: bool) -> None:
        assert isinstance(value, bool)
        self._highlight = value
        self.brush_color = self.main_color
        if value:
            self.brush_color.setAlpha(64)
        else:
            self.brush_color.setAlpha(32)
        self.brush = QBrush(self.brush_color)
        self.update(self.boundingRect())

    def paint(self, painter: QPainter, option, widget=None):
        # 仅在高亮或默认可见时绘制内容
        if not self.highlight and not self.default_visibility:
            return
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(self.polygon, Qt.FillRule.OddEvenFill)

2. 修复鼠标事件不响应问题

重写鼠标事件函数时,必须调用event.accept()告诉Scene该Item要处理这个事件,否则后续的鼠标事件(如mouseMove、mouseRelease)不会再传递给它。

修改后的鼠标事件代码:

def mouseMoveEvent(self, event):
    print("mouse move")
    event.accept()

def mousePressEvent(self, event):
    print("mouse press")
    event.accept()

def mouseReleaseEvent(self, event):
    print("mouse release")
    event.accept()

def mouseDoubleClickEvent(self, event):
    print("mouse double click")
    event.accept()

QGraphics框架的事件传播机制

  1. 事件流转:鼠标/键盘事件先由QGraphicsView接收,转换为场景坐标系事件后传递给QGraphicsScene。
  2. Item筛选:Scene根据鼠标位置筛选出所有包含该点的Item(按Z值从高到低排序),不可见或禁用的Item会被直接跳过。
  3. 事件分发:Scene依次将事件传给筛选后的Item:
    • 若Item调用event.accept(),事件传播终止,后续Item不会收到该事件。
    • 若Item调用event.ignore(),Scene会将事件传给下一个符合条件的Item。
  4. Hover事件特殊处理:需提前调用setAcceptHoverEvents(True)开启接收,当鼠标进入Item的shape()区域时触发hoverEnterEvent,离开时触发hoverLeaveEvent。

方案点评

  • 原方案用setVisible()控制可见性的错误:不可见Item会被Scene排除在事件分发列表外,导致无法接收任何事件。
  • 鼠标事件未响应的原因:重写事件函数时未调用event.accept(),Scene认为Item不处理该事件,后续事件不会再传递。
  • 最佳实践:通过paint()函数控制绘制逻辑,保持Item可见性为True,既能满足显示需求,又能确保事件正常传递。

内容的提问来源于stack exchange,提问作者Maxe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 11:37:32