如何在PySide6 QGraphicsView中实现可调整大小的矩形
可调整大小的QGraphicsRectItem实现方案
问题说明
继承QGraphicsRectItem实现可调整大小的矩形时遇到两个问题:
- 选中矩形后,调整手柄无法移动
- 不知道如何将手柄的移动同步到父矩形实现大小调整,且不想依赖
QGraphicsObject的信号槽机制
解决方案
问题1:手柄无法移动的修复
父项的ItemIsMovable标志会抢占鼠标事件,导致子手柄无法响应操作。需要:
- 给手柄添加
ItemIsSelectable标志,确保能接收鼠标事件 - 在父项的鼠标按下事件中,判断点击目标是否为手柄,若是则不执行父类的事件处理,让手柄接管事件
问题2:手柄移动同步矩形大小
通过给手柄重写鼠标事件,直接调用父项的调整方法,无需信号槽:
- 给每个手柄绑定对应的调整方向(如topLeft、topRight)
- 在手柄的
mouseMoveEvent中计算位移,调用父项的resizeRect方法更新矩形 - 父项更新矩形后,同步更新所有手柄的位置
修改后的完整代码
import sys from PySide6.QtCore import Qt, QPointF from PySide6.QtGui import QPen, QColor, QBrush from PySide6.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsItem class HandleItem(QGraphicsRectItem): def __init__(self, direction, parent=None): super().__init__(parent) self.direction = direction self.setPen(QPen(QColor(0, 0, 0))) self.setBrush(QBrush(Qt.white)) self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) self.startPos = QPointF() def mousePressEvent(self, event): self.startPos = event.scenePos() self.parentItem().startRect = self.parentItem().rect() super().mousePressEvent(event) def mouseMoveEvent(self, event): delta = event.scenePos() - self.startPos self.parentItem().resizeRect(self.direction, delta) super().mouseMoveEvent(event) class ResizableRectItem(QGraphicsRectItem): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setPen(QPen(Qt.black)) self.setBrush(QBrush(Qt.gray)) self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) self.handleSize = 8 self.handles = {} self.directions = ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'] self.createHandles() self.startRect = self.rect() def createHandles(self): rect = self.rect() for direction in self.directions: handle = HandleItem(direction, self) handle.setRect(-self.handleSize/2, -self.handleSize/2, self.handleSize, self.handleSize) handle.setVisible(False) handle.setPos(getattr(rect, direction)()) self.handles[direction] = handle def itemChange(self, change, value): if change == QGraphicsItem.GraphicsItemChange.ItemSelectedChange: for handle in self.handles.values(): handle.setVisible(bool(value)) return super().itemChange(change, value) def mousePressEvent(self, event): # 若点击的是手柄,不执行父类的移动逻辑 if event.item() in self.childItems(): return super().mousePressEvent(event) def resizeRect(self, direction, delta): rect = self.startRect if direction == 'topLeft': new_rect = rect.adjusted(delta.x(), delta.y(), 0, 0) elif direction == 'topRight': new_rect = rect.adjusted(0, delta.y(), delta.x(), 0) elif direction == 'bottomLeft': new_rect = rect.adjusted(delta.x(), 0, 0, delta.y()) elif direction == 'bottomRight': new_rect = rect.adjusted(0, 0, delta.x(), delta.y()) # 防止矩形缩小到负数 if new_rect.width() > 10 and new_rect.height() > 10: self.setRect(new_rect) self.updateHandlesPos() def updateHandlesPos(self): rect = self.rect() for direction, handle in self.handles.items(): handle.setPos(getattr(rect, direction)()) class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setGeometry(500, 500, 500, 500) self.view = QGraphicsView() self.scene = QGraphicsScene() self.view.setScene(self.scene) rectItem = ResizableRectItem(100, 100, 200, 200) self.scene.addItem(rectItem) self.setCentralWidget(self.view) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
代码说明
- 单独定义
HandleItem类封装手柄的鼠标事件,绑定调整方向 - 父项
ResizableRectItem的mousePressEvent过滤手柄的点击事件,避免父项抢占 resizeRect方法根据手柄方向调整矩形大小,同时限制最小尺寸防止异常updateHandlesPos方法在矩形大小变化后,同步更新所有手柄的位置
内容的提问来源于stack exchange,提问作者sunnytown
相关产品推荐
相关产品推荐

