PySide6/PyQt6旋转QGraphicsTextItem拖拽异常:角度超60度绕光标螺旋
问题:旋转QGraphicsTextItem后拖拽出现螺旋移动异常
开发一款可给图片添加水印并保存的GUI应用,需求支持长按拖拽移动水印、调整水印旋转角度。但发现当旋转角度超过60度时,拖拽水印会出现绕光标螺旋移动的异常,螺旋半径随拖拽距离和角度增大而增加。
复现代码
from PySide6.QtWidgets import ( QApplication, QGraphicsTextItem, QGraphicsView, QGraphicsScene, QSlider, QVBoxLayout, QWidget, QLabel ) from PySide6.QtCore import Qt import sys class MovableItem(QGraphicsTextItem): def __init__(self): super().__init__() self.setPlainText("Stack Overflow") def mousePressEvent(self, ev): if ev.button() == Qt.LeftButton: self.start_position = ev.pos() def mouseMoveEvent(self, ev): if ev.buttons() == Qt.LeftButton: delta = ev.pos() - self.start_position new_position = self.pos() + delta self.setPos(new_position) label_parameters = int(self.pos().x()), int(self.pos().y()) print(label_parameters) class Widget(QWidget): def __init__(self): super().__init__() self.resize(500, 500) self.graphical_window = QGraphicsView() self.scene = QGraphicsScene() self.graphical_window.setScene(self.scene) self.movable_item = MovableItem() self.movable_item.setTransformOriginPoint(self.movable_item.boundingRect().center()) self.scene.addItem(self.movable_item) self.slider = QSlider(Qt.Horizontal, minimum=0, maximum=359) self.slider.valueChanged.connect(self.movable_item.setRotation) self.label_rotation = QLabel('{}'.format(self.slider.value()), alignment=Qt.AlignCenter) self.slider.valueChanged.connect(lambda value: self.label_rotation.setText('{}'.format(self.slider.value()))) main_layout = QVBoxLayout() main_layout.addWidget(self.graphical_window) main_layout.addWidget(self.label_rotation) main_layout.addWidget(self.slider) self.setLayout(main_layout) app = QApplication(sys.argv) window = Widget() window.show() if __name__ == '__main__': app.exec()
拖拽异常输出示例
拖拽标签向下移动约100像素时的打印输出(x参数因手动拖拽略有变化):
角度30度(正常)
(3, 2) (4, 5) (6, 8) (6, 13) (6, 18) (6, 22) (7, 26) (6, 29) (6, 32) (6, 35) (7, 39) (7, 44) (6, 47) (7, 53) (8, 59) (8, 65) (7, 71) (7, 76) (6, 79) (7, 84) (8, 90)
角度75度(轻微异常)
(0, 0) (1, 2) (3, 5) (3, 9) (1, 13) (1, 16) (2, 20) (3, 24) (3, 29) (3, 32) (4, 36) (8, 42) (10, 50) (9, 61) (5, 69) (0, 73) (-1, 73) (0, 72) (4, 73) (7, 79) (9, 87) (10, 97) (6, 107) (1, 112) (2, 114) (3, 117) (4, 121) (6, 127) (6, 135) (4, 142) (1, 147) (0, 148) (4, 151) (15, 160) (23, 180) (26, 201) (21, 223) (8, 238)
角度90度(明显异常)
(0, -5) (6, -8) (14, -5) (21, 6) (22, 21) (17, 36) (6, 46) (-9, 45) (-21, 29) (-14, 5) (19, -12) (69, 4) (104, 46) (98, 106) (63, 160) (5, 179) (-61, 140) (-83, 55) (-17, -41) (133, -71) (299, 34) (361, 222) (258, 441) (-64, 544) (-386, 656) (-685, 768) (-1308, -63)
尝试过mapToParent、mapToScene、mapFromGlobal等位置映射方法但未解决,寻求PySide6/PyQt6下的可行方案。
解决方案
问题根源
当QGraphicsItem被旋转后,它的局部坐标系也跟着旋转了。原代码中ev.pos()获取的是鼠标在item局部坐标系中的位置,计算的delta是局部坐标系下的偏移量,直接加到item的全局位置self.pos()上,就会因为坐标系旋转导致偏移方向出现偏差,随着拖拽次数累积,最终形成螺旋移动。
修复代码
修改MovableItem类的鼠标事件处理,改用场景坐标系来计算偏移:
from PySide6.QtWidgets import ( QApplication, QGraphicsTextItem, QGraphicsView, QGraphicsScene, QSlider, QVBoxLayout, QWidget, QLabel ) from PySide6.QtCore import Qt import sys class MovableItem(QGraphicsTextItem): def __init__(self): super().__init__() self.setPlainText("Stack Overflow") def mousePressEvent(self, ev): if ev.button() == Qt.LeftButton: # 记录鼠标按下时的场景坐标,以及当前item的场景位置 self.start_scene_pos = ev.scenePos() self.item_start_pos = self.scenePos() super().mousePressEvent(ev) def mouseMoveEvent(self, ev): if ev.buttons() == Qt.LeftButton: # 计算鼠标在场景中的偏移量 delta_scene = ev.scenePos() - self.start_scene_pos # 设置item的新场景位置 self.setScenePos(self.item_start_pos + delta_scene) label_parameters = int(self.pos().x()), int(self.pos().y()) print(label_parameters) super().mouseMoveEvent(ev) class Widget(QWidget): def __init__(self): super().__init__() self.resize(500, 500) self.graphical_window = QGraphicsView() self.scene = QGraphicsScene() self.graphical_window.setScene(self.scene) self.movable_item = MovableItem() self.movable_item.setTransformOriginPoint(self.movable_item.boundingRect().center()) self.scene.addItem(self.movable_item) self.slider = QSlider(Qt.Horizontal, minimum=0, maximum=359) self.slider.valueChanged.connect(self.movable_item.setRotation) self.label_rotation = QLabel('{}'.format(self.slider.value()), alignment=Qt.AlignCenter) self.slider.valueChanged.connect(lambda value: self.label_rotation.setText('{}'.format(self.slider.value()))) main_layout = QVBoxLayout() main_layout.addWidget(self.graphical_window) main_layout.addWidget(self.label_rotation) main_layout.addWidget(self.slider) self.setLayout(main_layout) app = QApplication(sys.argv) window = Widget() window.show() if __name__ == '__main__': app.exec()
关键改动说明
mousePressEvent:记录鼠标按下时的场景坐标start_scene_pos,以及item当前的场景位置item_start_pos,完全避开item局部坐标系的影响。mouseMoveEvent:计算鼠标在场景中的偏移量delta_scene,直接用item初始场景位置加上该偏移量设置新位置,确保拖拽方向严格跟随鼠标在场景中的移动轨迹,不受item旋转角度干扰。- 调用父类的鼠标事件方法,保证QGraphicsItem的默认行为(如选中状态)正常工作。
修改后,无论item旋转多少度,拖拽时都会精准跟随鼠标移动,不会再出现螺旋偏移问题。
内容的提问来源于stack exchange,提问作者yakubszatkowski
相关产品推荐
相关产品推荐

