我想创建一个 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 参数发生轻微变化,因为我无法沿直线完美向下移动光标
(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)
(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)
(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 无法实现拖动旋转项目的功能 - 如果不能,是否有任何解决方法?
处理项目转换时,您必须始终考虑依赖本地位置的事件处理程序始终映射到项目。
这意味着任何转换最终都会改变项目位置的结果,以及与该位置相关的事件处理程序。
当您对某个项目应用旋转时,您获得的
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
标志即可。