PySide6/PyQt6 可移动 QGraphicsTextItem 在更改其角度时出现故障 - 当 QGraphicsTextItem 角度高于 60 时,围绕光标旋转

问题描述 投票:0回答:1

我想创建一个 GUI 应用程序,能够在照片上添加水印并保存它 - 我希望能够通过按住、按住和移动光标来移动此水印,我还需要能够旋转水印角度。但是,我注意到,当我将旋转设置为 60 以上时,尝试拖动标签时会出现错误,它开始围绕光标旋转,而螺旋半径会根据我拖动标签的距离以及拖动方式而增加。安装角度大。

这是简化的代码 - 尝试将角度设置为 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 像素时 print(label_parameters) 的示例输出 - y 参数发生变化,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,但没有成功。似乎 PyQT6/PySide6 无法实现拖动旋转项目的功能 - 如果不能,是否有任何解决方法?

python pyqt rotation position pyside
1个回答
0
投票

处理项目转换时,您必须始终考虑依赖本地位置的事件处理程序始终映射到项目。

这意味着任何转换最终都会改变项目位置的结果,以及与该位置相关的事件处理程序。

当您对某个项目应用旋转时,您获得的

event.pos()
已映射到局部坐标:请参阅
QGraphicsSceneMouseEvent::pos()
文档

返回项目坐标中的鼠标光标位置

这不仅重要,而且一致:鼠标事件处理程序根据鼠标位置根据其real状态获取事件的位置:如果单击旋转的矩形的左上角45°,您将始终获得

0, 0
位置。

现在:如果您只想移动项目,正确的方法是正确使用 QGraphicsItem 功能。在这种情况下,

QGraphicsItem.ItemIsMovable
标志,它根据鼠标移动自动正确地移动项目,无论它们的变换如何:

class MovableItem(QGraphicsTextItem):
    def __init__(self):
        super().__init__()
        self.setPlainText("Stack Overflow")
        self.setFlag(self.ItemIsMovable)

    # no further overrides needed

仍然存在的情况,您必须根据鼠标来管理移动。对于这些情况,您不能只依赖事件位置,然后调用

setPos()
,因为事件位置位于本地坐标中,而项目位置位于 parent 坐标中(请参阅有关 图形视图坐标系的文档) ,基于基本笛卡尔坐标系)。

在这些罕见的情况下,最好的解决方案是始终将事件坐标映射到场景,并最终将“增量”重新映射到父级(如果存在);然后,将之前的“起点”更新为映射到场景的新事件位置。

class MovableItem(QGraphicsTextItem):
    def __init__(self, parent):
        super().__init__(parent)
        self.setPlainText("Stack Overflow")

    def mousePressEvent(self, ev):
        if ev.button() == Qt.LeftButton:
            self.start_position = self.mapToScene(ev.pos())

    def mouseMoveEvent(self, ev):
        if ev.buttons() == Qt.LeftButton:
            newPos = self.mapToScene(ev.pos())
            if self.parentItem():
                delta = (
                    self.parentItem().mapFromScene(newPos) - 
                    self.parentItem().mapFromScene(self.start_position)
                )
            else:
                delta = newPos - self.start_position
            self.setPos(self.pos() + delta)
            label_parameters = int(self.pos().x()), int(self.pos().y())
            print(label_parameters)
            # IMPORTANT!
            self.start_position = newPos

请注意,这种方法有局限性:值得注意的是,它没有考虑最终限制位置变化的位置边界,通常在

itemChange
覆盖内完成。

最简单的解决方案是一开始给出的解决方案:只需在项目上设置

ItemIsMovable
标志即可。

© www.soinside.com 2019 - 2024. All rights reserved.