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

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()

关键改动说明

  1. mousePressEvent:记录鼠标按下时的场景坐标start_scene_pos,以及item当前的场景位置item_start_pos,完全避开item局部坐标系的影响。
  2. mouseMoveEvent:计算鼠标在场景中的偏移量delta_scene,直接用item初始场景位置加上该偏移量设置新位置,确保拖拽方向严格跟随鼠标在场景中的移动轨迹,不受item旋转角度干扰。
  3. 调用父类的鼠标事件方法,保证QGraphicsItem的默认行为(如选中状态)正常工作。

修改后,无论item旋转多少度,拖拽时都会精准跟随鼠标移动,不会再出现螺旋偏移问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 15:54:59