PyQt6中QGraphicsItem事件处理问题:不可见项的悬停与鼠标事件
PyQt6 QGraphicsItem事件处理:不可见项接收hover事件+鼠标事件响应问题
问题概述
- 场景包含两类元素:
QGraphicsPixmapItem实现的背景图、自定义多边形QGraphicsItem - 核心需求:
- 可见多边形:鼠标悬停时高亮,离开时恢复正常状态
- 不可见多边形:鼠标悬停时高亮显示,离开时恢复不可见状态
- 遇到的问题:
- 不可见/禁用的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框架的事件传播机制
- 事件流转:鼠标/键盘事件先由
QGraphicsView接收,转换为场景坐标系事件后传递给QGraphicsScene。 - Item筛选:Scene根据鼠标位置筛选出所有包含该点的Item(按Z值从高到低排序),不可见或禁用的Item会被直接跳过。
- 事件分发:Scene依次将事件传给筛选后的Item:
- 若Item调用
event.accept(),事件传播终止,后续Item不会收到该事件。 - 若Item调用
event.ignore(),Scene会将事件传给下一个符合条件的Item。
- 若Item调用
- Hover事件特殊处理:需提前调用
setAcceptHoverEvents(True)开启接收,当鼠标进入Item的shape()区域时触发hoverEnterEvent,离开时触发hoverLeaveEvent。
方案点评
- 原方案用
setVisible()控制可见性的错误:不可见Item会被Scene排除在事件分发列表外,导致无法接收任何事件。 - 鼠标事件未响应的原因:重写事件函数时未调用
event.accept(),Scene认为Item不处理该事件,后续事件不会再传递。 - 最佳实践:通过
paint()函数控制绘制逻辑,保持Item可见性为True,既能满足显示需求,又能确保事件正常传递。
内容的提问来源于stack exchange,提问作者Maxe
相关产品推荐
相关产品推荐

