You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 08:35:38