PyQt5可调整大小QGraphicsRectItem如何正确更新场景坐标位置
问题说明
实现可调整大小的QGraphicsRectItem时,边缘拖拽缩放的尺寸调整逻辑正常,但缩放后图元在场景中的位置无法正确更新:向上拖拽顶边缩放时,图元会出现持续向上偏移的问题,位置计算逻辑失效。
原有问题代码如下:
import typing import sys from PyQt5.QtGui import QPen, QBrush, QColor, QResizeEvent from PyQt5.QtCore import QRectF, QSize from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem, QGraphicsRectItem, QMainWindow, QVBoxLayout, QWidget class ResizableRect(QGraphicsRectItem): def __init__(self, *args): super().__init__(*args) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setPen(QPen(QBrush(QColor('blue')), 5)) self.selected_edge = None self.click_pos = self.click_rect = None def mousePressEvent(self, event): """ The mouse is pressed, start tracking movement. """ self.click_pos = event.pos() self.newY = self.pos().y() rect = self.rect() if abs(rect.left() - self.click_pos.x()) < 5: self.selected_edge = 'left' elif abs(rect.right() - self.click_pos.x()) < 5: self.selected_edge = 'right' elif abs(rect.top() - self.click_pos.y()) < 5: self.selected_edge = 'top' elif abs(rect.bottom() - self.click_pos.y()) < 5: self.selected_edge = 'bottom' else: self.selected_edge = None self.click_pos = event.pos() self.click_rect = rect super().mousePressEvent(event) def mouseMoveEvent(self, event): """ Continue tracking movement while the mouse is pressed. """ # Calculate how much the mouse has moved since the click. pos = event.pos() x_diff = pos.x() - self.click_pos.x() y_diff = pos.y() - self.click_pos.y() # Start with the rectangle as it was when clicked. rect = QRectF(self.click_rect) # Then adjust by the distance the mouse moved. if self.selected_edge is None: rect.translate(x_diff, y_diff) elif self.selected_edge == 'top': rect.adjust(0, y_diff, 0, 0) # Test when resize rectangle upward; not working properly for now if y_diff < 0: newCenter = (rect.bottom() - pos.y()) / 2 self.newY = self.pos().y() - newCenter elif self.selected_edge == 'left': rect.adjust(x_diff, 0, 0, 0) elif self.selected_edge == 'bottom': rect.adjust(0, 0, 0, y_diff) elif self.selected_edge == 'right': rect.adjust(0, 0, x_diff, 0) # Also check if the rectangle has been dragged inside out. if rect.width() < 5: if self.selected_edge == 'left': rect.setLeft(rect.right() - 5) else: rect.setRight(rect.left() + 5) if rect.height() < 5: if self.selected_edge == 'top': rect.setTop(rect.bottom() - 5) else: rect.setBottom(rect.top() + 5) # Finally, update the rect that is now guaranteed to stay in bounds. self.setY(self.newY) self.setRect(rect) def mouseReleaseEvent(self, event): # for printing only i.e., after resizing print(f"item.pos(): {self.pos()}") class MainWindow(QMainWindow): def __init__(self): super().__init__() central = QWidget(self) self.setCentralWidget(central) self.rect = ResizableRect(-100, -50, 200, 100) scene = QGraphicsScene(0, 0, 300, 300) scene.addItem(self.rect) self.view = QGraphicsView(central) self.view.setScene(scene) layout = QVBoxLayout(central) layout.addWidget(self.view) def main(): app = QApplication(sys.argv) window = MainWindow() window.show() app.exec_() main()
错误原因
- 位置计算公式完全不成立:拖拽顶边时使用的中心点计算逻辑不符合图元坐标系规则,且每次鼠标移动都基于当前实时的
self.pos().y()做增量计算,没有以鼠标按下瞬间的初始位置为基准,导致偏移量持续累加,出现图元漂移。 - 逻辑覆盖不全:仅处理了向上拖拽顶边(y_diff<0)的场景,向下拖拽顶边缩小高度时完全没有更新位置。
- 遗漏边缘位置同步:拖拽左边缩放和顶边逻辑一致,需要同步更新图元x坐标,原有代码未处理这部分。
修正逻辑
拖拽边缘缩放的核心规则:拖拽某一条边时,对面的边在场景中的位置保持固定不动。
- 拖拽顶边时,底边位置固定:鼠标按下时先记录底边在场景中的初始y坐标,缩放后新的图元y坐标 = 底边固定y坐标 - 新矩形自身坐标系下的bottom值。
- 拖拽左边时,右边位置固定:鼠标按下时先记录右边在场景中的初始x坐标,缩放后新的图元x坐标 = 右边固定x坐标 - 新矩形自身坐标系下的right值。
- 拖拽底边/右边时,顶边/左边位置天然固定,不需要修改图元坐标,直接调整rect尺寸即可。
- 所有计算都基于鼠标按下时存储的初始状态,不在鼠标移动过程中用实时位置做增量累加,从根源避免漂移。
修正后完整代码
import sys from PyQt5.QtGui import QPen, QBrush, QColor from PyQt5.QtCore import QRectF from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem, QGraphicsRectItem, QMainWindow, QVBoxLayout, QWidget class ResizableRect(QGraphicsRectItem): def __init__(self, *args): super().__init__(*args) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setPen(QPen(QBrush(QColor('blue')), 5)) self.selected_edge = None # 存储鼠标按下时的初始状态 self.click_pos = None self.click_rect = None self.initial_pos = None # 存储固定边的场景坐标 self.fixed_x = None self.fixed_y = None def mousePressEvent(self, event): self.click_pos = event.pos() rect = self.rect() self.initial_pos = self.pos() # 判断点击的边缘,记录对侧固定边坐标 if abs(rect.left() - self.click_pos.x()) < 5: self.selected_edge = 'left' self.fixed_x = self.initial_pos.x() + rect.right() elif abs(rect.right() - self.click_pos.x()) < 5: self.selected_edge = 'right' self.fixed_x = None elif abs(rect.top() - self.click_pos.y()) < 5: self.selected_edge = 'top' self.fixed_y = self.initial_pos.y() + rect.bottom() elif abs(rect.bottom() - self.click_pos.y()) < 5: self.selected_edge = 'bottom' self.fixed_y = None else: self.selected_edge = None self.fixed_x = None self.fixed_y = None self.click_rect = rect super().mousePressEvent(event) def mouseMoveEvent(self, event): pos = event.pos() x_diff = pos.x() - self.click_pos.x() y_diff = pos.y() - self.click_pos.y() rect = QRectF(self.click_rect) new_x = self.initial_pos.x() new_y = self.initial_pos.y() if self.selected_edge is None: # 整体拖拽移动 new_x = self.initial_pos.x() + x_diff new_y = self.initial_pos.y() + y_diff elif self.selected_edge == 'top': rect.adjust(0, y_diff, 0, 0) # 保持底边位置固定 new_y = self.fixed_y - rect.bottom() elif self.selected_edge == 'left': rect.adjust(x_diff, 0, 0, 0) # 保持右边位置固定 new_x = self.fixed_x - rect.right() elif self.selected_edge == 'bottom': rect.adjust(0, 0, 0, y_diff) elif self.selected_edge == 'right': rect.adjust(0, 0, x_diff, 0) # 限制最小尺寸 if rect.width() < 5: if self.selected_edge == 'left': rect.setLeft(rect.right() - 5) new_x = self.fixed_x - rect.right() else: rect.setRight(rect.left() + 5) if rect.height() < 5: if self.selected_edge == 'top': rect.setTop(rect.bottom() - 5) new_y = self.fixed_y - rect.bottom() else: rect.setBottom(rect.top() + 5) # 同步更新位置和矩形尺寸 self.setPos(new_x, new_y) self.setRect(rect) def mouseReleaseEvent(self, event): print(f"item.pos(): {self.pos()}") super().mouseReleaseEvent(event) class MainWindow(QMainWindow): def __init__(self): super().__init__() central = QWidget(self) self.setCentralWidget(central) self.rect = ResizableRect(-100, -50, 200, 100) scene = QGraphicsScene(0, 0, 300, 300) scene.addItem(self.rect) self.view = QGraphicsView(central) self.view.setScene(scene) layout = QVBoxLayout(central) layout.addWidget(self.view) def main(): app = QApplication(sys.argv) window = MainWindow() window.show() app.exec_() if __name__ == "__main__": main()
修正后拖拽任意边缩放时,对侧固定边不会发生位移,图元位置和尺寸完全跟随鼠标操作,无漂移问题。
内容的提问来源于stack exchange,提问作者MinhTNguyen
相关产品推荐
相关产品推荐

