在我的程序中,我有一个 QDialog,它显示一张可以缩放和平移的图片,如果单击鼠标右键平移,则可以绘制一个矩形。当您绘制矩形并到达视图边缘时,图片应该移动,以便您可以继续绘制矩形。目前,只有当您不断轻微移动光标以触发 mouseMoveEvent 时,它才有效。
如果我尝试在鼠标不移动时以编程方式调用 mouseMoveEvent ,为了保持图片移动,它会移动图片,但由于某种原因视图不会更新,因此您只能在再次移动鼠标后看到图片已移动。所以基本上如果你将鼠标放在边缘一段时间而不移动它,然后再移动它,图片会突然移动很多。
有趣的是,当我以编程方式调用 mouseMoveEvent 时,由于某种原因 event.position() 发生了变化,即使事件始终是相同的。
这是一个最小的可重现示例:
adjustment_view 函数是原来的函数,需要上下移动光标才能保持图片移动
adjustment_view_loop 函数是我尝试以编程方式使其工作
我使用Python 3.10.7和PySide6.3.2
from PySide6.QtCore import Signal, Qt, QRectF, QPoint, QRect
from PySide6.QtGui import QPainter, QPixmap, QImage, QPen, QCursor, QMouseEvent
from PySide6.QtWidgets import (
QGraphicsView,
QGraphicsScene,
QGraphicsPixmapItem,
QGraphicsRectItem,
QApplication,
QDialog,
QDialogButtonBox,
QVBoxLayout,
QHBoxLayout,
)
import warnings
import time
import sys
class MyDialog(QDialog):
MIN_WIDTH = 900
MIN_HEIGHT = int(MIN_WIDTH * 2 / 3)
def __init__(self, path, parent=None):
super().__init__(parent)
self.path = path
self.setFixedSize(self.MIN_WIDTH, self.MIN_HEIGHT)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.rejected.connect(self.reject)
self.buttonBox.accepted.connect(self.accept)
self.layout = QVBoxLayout()
self.image_viewer = PhotoViewer(self)
image = QImage(path)
self.pixmap = QPixmap(image)
self.image_viewer.set_photo(self.pixmap)
bottom_layout = QHBoxLayout()
bottom_layout.addWidget(self.buttonBox)
self.layout.addWidget(self.image_viewer)
self.layout.addLayout(bottom_layout)
self.setLayout(self.layout)
def exec(self):
self.image_viewer.viewport().setMinimumSize(
self.MIN_WIDTH - 100, self.MIN_HEIGHT - 100
)
self.image_viewer.fit_in_view()
self.image_viewer.calc_delta()
super().exec()
class PhotoViewer(QGraphicsView):
"""
class for displaying the pictures
"""
DELTA = 10
mouse_event_signal = Signal()
def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
warnings.filterwarnings("error")
self.zoom = 0
self._empty = True
self._photo = QGraphicsPixmapItem()
self._photo.setTransformationMode(Qt.SmoothTransformation)
self.set_scene()
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.Antialiasing)
def set_scene(self):
self.scene = SelectColorBarDialogScene(self)
self.scene.addItem(self._photo)
self.setScene(self.scene)
def fit_in_view(self, scale=True):
"""
Resizes the photo to fit in the view
"""
rect = QRectF(self._photo.pixmap().rect())
if not rect.isNull():
self.setSceneRect(rect)
unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
factor = min(
viewrect.width() / scenerect.width(),
viewrect.height() / scenerect.height(),
)
self.scale(factor, factor)
self.zoom = 0
def mousePressEvent(self, event):
if event.buttons() & Qt.RightButton:
self.update()
super(PhotoViewer, self).mousePressEvent(event)
def mouseMoveEvent(self, event, pass_adjust=False):
super(PhotoViewer, self).mouseMoveEvent(event)
if event.buttons() & Qt.RightButton:
if not self.scene.begin.isNull() and not self.scene.destination.isNull():
if not pass_adjust:
# self.adjust_view(event)
self.adjust_view_loop(event)
print(event.globalPosition())
print(event.position())
print("_______________")
self.update()
#! When programatically changed, the mouse pos changes even though cursor is not moved
def adjust_view_loop(self, event):
edges = self.check_if_point_on_edge(self.scene.destination)
if (
edges
and self.scene.destination.x() < self.photo_size.width() - self.DELTA
and self.scene.destination.y() < self.photo_size.height() - self.DELTA
and self.scene.destination.x() > self.DELTA
and self.scene.destination.y() > self.DELTA
):
i = 0
self.cursor_pos = QCursor.pos()
while True:
i += 1
old_view = self.mapToScene(self.viewport().rect()).boundingRect()
new_view = [0, 0, 0, 0]
for _ in range(edges.bit_count()):
if edges & 1 == 1:
new_view[1] = -self.DELTA
new_view[3] = -self.DELTA
elif edges & 4 == 4:
new_view[1] = self.DELTA
new_view[3] = self.DELTA
elif edges & 2 == 2:
new_view[0] = -self.DELTA
new_view[2] = -self.DELTA
elif edges & 8 == 8:
new_view[0] = self.DELTA
new_view[2] = self.DELTA
new_view_rect = old_view.adjusted(
new_view[0], new_view[1], new_view[2], new_view[3]
)
self.fitInView(new_view_rect, Qt.KeepAspectRatio)
x = self.scene.destination.x()
y = self.scene.destination.y()
self.set_rect()
if i % 2 == 0:
new_event = QMouseEvent(
event.type(),
QPoint(event.position().x(), event.position().y() + 1),
QPoint(
event.globalPosition().x(), event.globalPosition().y() + 1
),
event.button(),
event.buttons(),
event.modifiers(),
)
else:
new_event = QMouseEvent(
event.type(),
QPoint(event.position().x(), event.position().y() - 1),
QPoint(
event.globalPosition().x(), event.globalPosition().y() - 1
),
event.button(),
event.buttons(),
event.modifiers(),
event.pointingDevice(),
)
self.mouseMoveEvent(new_event, True)
self.update()
print("Looping...")
time.sleep(0.5)
if not self.on_loop_signal():
break
self.set_rect()
def adjust_view(self, event):
edges = self.check_if_point_on_edge(self.scene.destination)
if (
edges
and self.scene.destination.x() < self.photo_size.width() - self.DELTA
and self.scene.destination.y() < self.photo_size.height() - self.DELTA
and self.scene.destination.x() > self.DELTA
and self.scene.destination.y() > self.DELTA
):
old_view = self.mapToScene(self.viewport().rect()).boundingRect()
new_view = [0, 0, 0, 0]
for _ in range(edges.bit_count()):
if edges & 1 == 1:
new_view[1] = -self.DELTA
new_view[3] = -self.DELTA
elif edges & 4 == 4:
new_view[1] = self.DELTA
new_view[3] = self.DELTA
elif edges & 2 == 2:
new_view[0] = -self.DELTA
new_view[2] = -self.DELTA
elif edges & 8 == 8:
new_view[0] = self.DELTA
new_view[2] = self.DELTA
new_view_rect = old_view.adjusted(
new_view[0], new_view[1], new_view[2], new_view[3]
)
self.fitInView(new_view_rect, Qt.KeepAspectRatio)
x = self.scene.destination.x()
y = self.scene.destination.y()
self.set_rect()
self.set_rect()
def set_rect(self):
for item in self.scene.items():
if isinstance(item, QGraphicsRectItem):
self.scene.removeItem(item)
self.rect = QRect(self.scene.begin, self.scene.destination)
self.scene.addRect(self.rect.normalized(), QPen(Qt.red))
def get_rect(self):
return self.rect.normalized()
def mouseReleaseEvent(self, event):
if event.button() & Qt.RightButton:
self.set_rect()
self.update()
super(PhotoViewer, self).mouseReleaseEvent(event)
def check_if_point_on_edge(self, point):
rect = self.mapToScene(
self.viewport().rect()
).boundingRect() # left, top, width, height
edges = 0 # top = 1, bottom = 2, left = 4, right = 8
internal_rect = rect.adjusted(self.DELTA, self.DELTA, -self.DELTA, -self.DELTA)
if rect.contains(point) and not internal_rect.contains(point):
if point.x() < internal_rect.left():
edges = edges | 2
elif point.x() > internal_rect.right():
edges = edges | 8
if point.y() < internal_rect.top():
edges = edges | 1
elif point.y() > internal_rect.bottom():
edges = edges | 4
return edges
def wheelEvent(self, event):
self.calc_delta()
if event.angleDelta().y() > 0:
factor = 1.25
self.zoom += 1
else:
factor = 0.8
self.zoom -= 1
if self.zoom > 0:
self.scale(factor, factor)
elif self.zoom == 0:
self.fit_in_view()
else:
self.zoom = 0
def calc_delta(self):
rect_width = self.mapToScene(self.viewport().rect()).boundingRect().width()
self.DELTA = int(rect_width / 25)
def set_photo(self, pixmap=None):
"""
Sets the photo to be displayed, if pixmap is None, the photo is set to empty
"""
if pixmap and not pixmap.isNull():
self._empty = False
self.setDragMode(QGraphicsView.ScrollHandDrag)
self._photo.setPixmap(pixmap)
else:
self._empty = True
self.setDragMode(QGraphicsView.NoDrag)
self._photo.setPixmap(QPixmap())
self.photo_size = QRectF(self._photo.pixmap().rect())
def on_loop_signal(self):
if self.cursor_pos != QCursor.pos():
self.mouse_event_signal.emit()
return False
return True
class SelectColorBarDialogScene(QGraphicsScene):
def __init__(self, parent):
super(SelectColorBarDialogScene, self).__init__(parent)
self.begin, self.destination = QPoint(), QPoint()
def mousePressEvent(self, event):
if event.buttons() & Qt.RightButton:
self.begin = event.scenePos().toPoint()
self.destination = self.begin
self.update()
super(SelectColorBarDialogScene, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() & Qt.RightButton:
self.destination = event.scenePos().toPoint()
self.update()
super(SelectColorBarDialogScene, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if event.button() & Qt.RightButton:
self.begin, self.destination = QPoint(), QPoint()
self.update()
super(SelectColorBarDialogScene, self).mouseReleaseEvent(event)
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = MyDialog(path=None) #! set picture path here
dialog.exec()
sys.exit(app.exec_())