PySide6中QGraphicsView缩放时AnchorUnderMouse异常问题(疑似Bug)
QGraphicsView缩放锚点异常问题分析
需求目标
实现QGraphicsView缩放时,鼠标下方的场景位置始终保持在鼠标正下方。
复现代码
from PySide6.QtCore import QPoint from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QApplication import math class MyGraphicsView(QGraphicsView): def wheelEvent(self, event): self.setTransformationAnchor(self.ViewportAnchor.AnchorUnderMouse) self.setResizeAnchor(self.ViewportAnchor.AnchorUnderMouse) if event.angleDelta().y() > 0: self.scale(1.1, 1.1) elif event.angleDelta().y() < 0: self.scale(1 / 1.1, 1 / 1.1) self.setTransformationAnchor(self.ViewportAnchor.NoAnchor) self.setResizeAnchor(self.ViewportAnchor.NoAnchor) class MyTestScene(QGraphicsScene): def drawBackground(self, painter, rect): left = int(math.floor(rect.left())) top = int(math.floor(rect.top())) right = int(math.ceil(rect.right())) bottom = int(math.ceil(rect.bottom())) first_left = left - (left % 100) first_top = top - (top % 100) for x in range(first_left, right + 1, 100): for y in range(first_top, bottom + 1, 100): painter.drawEllipse(QPoint(x, y), 2.5, 2.5) painter.drawText(x, y, f"{x},{y}") if __name__ == '__main__': app = QApplication([]) scene = MyTestScene() scene.setSceneRect(-2000, -2000, 4000, 4000) view = MyGraphicsView() view.setScene(scene) view.setGeometry(100, 100, 900, 600) view.setVisible(True) # view.setInteractive(False) app.exec()
异常现象
- 交互模式下,首次滚轮缩放前点击视图,功能正常;
- 交互模式下未点击视图,首次滚轮事件会触发场景跳转,将(0,0)位置移至鼠标下方,后续操作恢复正常;
- 视图设置为非交互模式时,每次滚轮事件都会触发上述跳转。
测试环境:Windows系统,PySide6.7.2+Python3.12.4、PySide6.8.1+Python3.13.1。
成因分析
- 焦点与鼠标跟踪机制:QGraphicsView在未获得焦点(未点击)或非交互模式下,不会主动跟踪鼠标在场景中的实时位置。此时使用
AnchorUnderMouse锚点时,无法获取正确的鼠标对应场景坐标,会 fallback 到场景原点(0,0)作为缩放锚点,导致场景跳转。 - 交互模式的影响:默认交互模式下,点击视图后,视图获得焦点并启动鼠标位置跟踪,
AnchorUnderMouse能正确获取鼠标对应的场景点,缩放行为符合预期;而非交互模式会禁用包括鼠标位置跟踪在内的大部分交互逻辑,导致每次缩放都无法获取正确锚点。 - 临时锚点设置的局限性:代码中临时切换锚点的逻辑本身没有错误,但依赖Qt内部的鼠标位置跟踪状态,当跟踪状态异常时就会触发错误行为。
结论
该问题属于Qt在焦点/交互模式与锚点缩放结合时的设计缺陷(未明确文档化的行为),而非代码疏漏。Qt的AnchorUnderMouse依赖视图的鼠标跟踪状态,而该状态在视图未激活或非交互模式下无法正常工作。
解决方案
方案1:确保视图启动时获得焦点
在__main__块中添加view.setFocus(),让视图初始化时就具备焦点,触发鼠标跟踪:
if __name__ == '__main__': app = QApplication([]) scene = MyTestScene() scene.setSceneRect(-2000, -2000, 4000, 4000) view = MyGraphicsView() view.setScene(scene) view.setGeometry(100, 100, 900, 600) view.setFocus() # 添加这行 view.setVisible(True) # view.setInteractive(False) app.exec()
方案2:手动计算缩放锚点(不依赖Qt内置锚点)
完全绕过AnchorUnderMouse,手动计算鼠标对应的场景坐标,缩放后调整视图位置确保锚点不变,不受焦点和交互模式影响:
class MyGraphicsView(QGraphicsView): def wheelEvent(self, event): # 获取鼠标在视图中的位置 mouse_view_pos = event.position().toPoint() # 转换为场景坐标 mouse_scene_pos = self.mapToScene(mouse_view_pos) # 计算缩放因子 scale_factor = 1.1 if event.angleDelta().y() > 0 else 1 / 1.1 # 执行缩放 self.scale(scale_factor, scale_factor) # 计算缩放后鼠标对应的新场景坐标 new_mouse_scene_pos = self.mapToScene(mouse_view_pos) # 计算偏移量,让原场景点回到鼠标下方 delta = mouse_scene_pos - new_mouse_scene_pos # 平移视图修正位置 self.translate(delta.x(), delta.y())
内容的提问来源于stack exchange,提问作者julian
相关产品推荐
相关产品推荐

