我对 PyQt6 相当陌生,我对 QGraphicsItems 中的事件处理感兴趣。
我有 QGraphicsItem(s),在 QGraphicsScene 和带有 QGraphicsView 的视图上绘制。我主要在场景中绘制两种类型的对象:
我发现了hoverEvent,这对我来说非常有用,但是当我绘制场景时,并非所有多边形都是可见的。然而,它们都应该对鼠标做出反应。
我想要的行为是:
如果多边形可见,当鼠标进入时,它会突出显示,当鼠标离开时,它会恢复正常。
如果多边形不可见,当鼠标进入时,它会突出显示,当鼠标离开时,它会恢复为不可见。
问题是事件不会分发到不可见或禁用的项目。如何强制传播悬停事件? 同样,我无法处理释放和移动的 mouseEvent(即使多边形可见)。
如果专家能够解释 QEvent 一般是如何传播的,我也会非常高兴,因为我还没有找到任何非常详细(或解释良好)的文档。 也欢迎对实施选择进行评论和评论。
这是我的示例代码(更改
default_visibility
进行测试)
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()
# draw a background image
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)
# draw a custom polygon
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)
# init the viewport
view = QGraphicsView(scene)
view.setGeometry(0, 0, 600, 600)
view.show()
app.exec()
我尝试子类化 QGraphicsScene 来手动管理事件传播。据我了解,场景的作用是重新格式化事件并将其分配给项目,但我没有成功,无论如何,我认为这不是正确的方法。
虽然从理论上讲,甚至可以将鼠标事件传播到隐藏的项目,但这样做是错误的,除了困难而且可能效率低下或不可靠(或两者兼而有之):您需要迭代场景中的所有项目,包括隐藏的项目,然后确定它们是否“正常”隐藏,或者它们仍然应该对鼠标事件做出反应;这适用于任何鼠标移动,考虑到它是在 Python 中完成的,这也是非常低效的。
就像隐藏小部件发生的情况一样,隐藏项目:
也是如此隐形物品不会被绘制,也不会接收任何事件。特别是,鼠标事件直接穿过不可见的项目,并传递到可能位于后面的任何项目。不可见的项目也是不可选择的,它们无法获取输入焦点,并且不会被 QGraphicsScene 的项目位置功能检测到。
由于 Qt 可见性实际上涉及其他方面,并且实际上,您只想使该项目不可见但仍可用于碰撞检测(包括鼠标事件),因此解决方案实际上非常明显:如果您不想看到它,不要画它。
您已经覆盖了
paint()
函数,因此您只需要在需要时实际绘制它即可。这可以通过自定义私有标志来实现,并且可能使用类似于 setVisible()
: 的专用函数来实现
class QGraphicsCustomPolygonItem(QGraphicsItem):
_visible = True
def __init__(self, polygon, default_visibility):
...
# use this instead of setVisible()
self.makeVisible(default_visibility)
def makeVisible(self, visible):
if self._visible != visible:
self._visible = visible
self.update()
def paint(self, painter: QPainter, option, widget=None):
if not self._visible:
return
...
您显然可以通过覆盖
setVisible()
来做到这一点(与 QWidget 的对应部分不同,这将 not 是真正的覆盖),但通常最好使用单独的函数来实现此类目的。
无论如何,如果为了简单起见您仍然想使用
setVisible()
,请记住,如果您随后想使用默认行为(使其对 Qt 及其事件实际上隐藏或可见),则应该调用默认实现(super().setVisible(...)
):
class QGraphicsCustomPolygonItem(QGraphicsItem):
_visible = True
...
def setVisible(self, visible):
if self._visible != visible:
self._visible = visible
super().update()
def setVisibleForQt(self, visible):
super().setVisible(visible)
关于您的鼠标移动/释放说明(基于您的原始代码),问题是由于覆盖
mousePressEvent()
并仍然调用默认实现而引起的。由于默认行为忽略鼠标按钮按下,与小部件类似,该项目将不会接收进一步的移动或释放事件。如果您想接收移动和释放事件,则必须接受该事件。如果您仍想调用默认实现,请确保在执行此操作后调用 event.accept()
after。
请注意,为 QPolygonF 子类化 QGraphicsItem 没有什么意义,因为 Qt 已经提供了 QGraphicsPolygonItem。您的实现也是不准确的,因为它没有正确考虑有关笔宽度的
boundingRect()
指示,如果项目几何形状发生变化甚至在滚动时可能会导致绘画伪像(“重影”),特别是在视图使用缩放时变形。class QGraphicsCustomPolygonItem(QGraphicsPolygonItem):
_visible = True
_highlight = False
def __init__(self, polygon, default_visibility):
super().__init__(polygon)
self.setAcceptHoverEvents(True)
self.default_visibility = default_visibility
self.main_color = QColor(Qt.red)
self.setPen(self.main_color)
self.brush_color = QColor(self.main_color)
self.brush_color.setAlpha(32)
self.setBrush(self.brush_color)
self.makeVisible(default_visibility)
@property
def highlight(self) -> bool:
return self._highlight
@highlight.setter
def highlight(self, value: bool) -> None:
assert isinstance(value, bool)
if self._highlight == value:
return
self._highlight = value
if value:
self.brush_color.setAlpha(64)
else:
self.brush_color.setAlpha(32)
self.setBrush(self.brush_color)
self.makeVisible(value or self.default_visibility)
def makeVisible(self, visible):
if self._visible != visible:
self._visible = visible
self.update()
def paint(self, painter: QPainter, option, widget=None):
if self._visible:
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
super().paint(painter, option, widget)
最后,如果你想要一个由白色和黑色像素组成的“假灰色”网格图案,设置每个像素颜色的效率极低(特别是考虑到Python瓶颈)。
Qt 已经通过
Qt.BrushStyle
提供了此类基本模式,因此您只需在像素图上使用 QPainter 以及 fillRect()
:
pixmap = QPixmap(300, 300)
pixmap.fill(Qt.white)
qp = QPainter(pixmap)
qp.fillRect(pixmap.rect(), QBrush(Qt.BrushStyle.Dense4Pattern))
qp.end()
scene.addPixmap(pixmap)