在插入小部件时重新调整自定义 QGraphicsWIdget 的大小

Readjust the Custom QGraphicsWIdget's size on inserting widget

我创建了一个自定义 QGraphicsWidget,它能够在场景中调整小部件的大小。我还可以将预定义的小部件(例如按钮、标签等)添加到我的自定义小部件中。我现在有两个问题。 第一个是小部件在插入新的 labelLineEdit 小部件时不会更改大小(重新调整),因此新插入的小部件位于自定义小部件边框之外。

第二个问题是当我尝试将QGraphicsLayoutsetContentMargins更改为0以外的值时遇到的。例如QGraphicsLayout.setContentMargins(1, 1, 1, 20)会延迟[=12中的光标=] 小部件。

这是图片。

(拖动灰色三角形改变大小)

import sys

from PyQt5 import QtWidgets, QtCore, QtGui, Qt
from PyQt5.QtCore import Qt, QRectF, QPointF
from PyQt5.QtGui import QBrush, QPainterPath, QPainter, QColor, QPen, QPixmap
from PyQt5.QtWidgets import QGraphicsRectItem, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem

class Container(QtWidgets.QWidget):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.layout = QtWidgets.QVBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.setStyleSheet('Container{background:transparent;}')


class GraphicsFrame(QtWidgets.QGraphicsWidget):

    def __init__(self, *args, **kwargs):
        super(GraphicsFrame, self).__init__()

        x, y, h, w = args
        rect = QRectF(x, y, h, w)
        self.setGeometry(rect)

        self.setMinimumSize(150, 150)
        self.setMaximumSize(400, 800)

        self.setAcceptHoverEvents(True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        self.mousePressPos = None
        self.mousePressRect = None
        self.handleSelected = None

        self.polygon = QtGui.QPolygon([
            QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-20)),
            QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-10)),
            QtCore.QPoint(int(self.rect().width()-20), int(self.rect().height()-10))
        ])

        graphic_layout = QtWidgets.QGraphicsLinearLayout(Qt.Vertical, self)
        graphic_layout.setContentsMargins(0, 0, 0, 20)  # changing this will cause the second problem

        self.container = Container()

        proxyWidget = QtWidgets.QGraphicsProxyWidget(self)
        proxyWidget.setWidget(self.container)

        graphic_layout.addItem(proxyWidget)

        self.contentLayout = QtWidgets.QFormLayout()
        self.contentLayout.setContentsMargins(10, 10, 20, 20)
        self.contentLayout.setSpacing(5)

        self.container.layout.addLayout(self.contentLayout)
        self.options = []

    def addOption(self, color=Qt.white, lbl=None, widget=None):
        self.insertOption(-1, lbl, widget, color)

    def insertOption(self, index, lbl, widget, color=Qt.white):
        if index < 0:
            index = self.contentLayout.count()
        self.contentLayout.addRow(lbl, widget)

        self.options.insert(index, (widget, color))


    def update_polygon(self):
        self.polygon = QtGui.QPolygon([
            QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 20)),
            QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 10)),
            QtCore.QPoint(int(self.rect().width() - 20), int(self.rect().height() - 10))
        ])

    def hoverMoveEvent(self, event):
        if self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill):
            self.setCursor(Qt.SizeFDiagCursor)

        else:
            self.unsetCursor()

        super(GraphicsFrame, self).hoverMoveEvent(event)


    def mousePressEvent(self, event):

        self.handleSelected = self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill)

        if self.handleSelected:
            self.mousePressPos = event.pos()
            self.mousePressRect = self.boundingRect()

        super(GraphicsFrame, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):

        if self.handleSelected:
            self.Resize(event.pos())

        else:
            super(GraphicsFrame, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):

        super(GraphicsFrame, self).mouseReleaseEvent(event)
        self.handleSelected = False
        self.mousePressPos = None
        self.mousePressRect = None
        self.update()

    def paint(self, painter, option, widget):

        painter.save()

        painter.setBrush(QBrush(QColor(37, 181, 247)))
        pen = QPen(Qt.white)
        pen.setWidth(2)

        if self.isSelected():
            pen.setColor(Qt.yellow)

        painter.setPen(pen)
        painter.drawRoundedRect(self.rect(), 4, 4)

        painter.setPen(QtCore.Qt.white)
        painter.setBrush(QtCore.Qt.gray)
        painter.drawPolygon(self.polygon)

        super().paint(painter, option, widget)

        painter.restore()

    def Resize(self, mousePos):
        """
        Perform shape interactive resize.
        """
        if self.handleSelected:
            self.prepareGeometryChange()
            width, height = self.geometry().width()+(mousePos.x()-self.mousePressPos.x()),\
                            self.geometry().height()+(mousePos.y()-self.mousePressPos.y())

            self.setGeometry(QRectF(self.geometry().x(), self.geometry().y(), width, height))
            self.contentLayout.setGeometry(QtCore.QRect(0, 30, width-10, height-20))

            self.mousePressPos = mousePos
            self.update_polygon()
            self.updateGeometry()


def main():

    app = QApplication(sys.argv)

    grview = QGraphicsView()
    scene = QGraphicsScene()
    grview.setViewportUpdateMode(grview.FullViewportUpdate)
    scene.addPixmap(QPixmap('01.png'))
    grview.setScene(scene)

    item = GraphicsFrame(0, 0, 300, 150)
    scene.addItem(item)

    item.addOption(Qt.green, lbl=QtWidgets.QLabel('I am a label'), widget=QtWidgets.QLineEdit())
    item.addOption(lbl=QtWidgets.QLabel('why'), widget=QtWidgets.QLineEdit())
    item.addOption(lbl=QtWidgets.QLabel('How'), widget=QtWidgets.QLineEdit())
    item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
    item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
    item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())

    item2 = GraphicsFrame(50, 50, 300, 150)
    scene.addItem(item2)

    grview.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
    grview.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

正如已经不止一次向您建议的那样,如果您只是将 QGraphicsWidget 与 QGraphicsLayout 一起使用来嵌入 QGraphicsProxyWidget,则这不是一个好主意,因为在更改几何图形时您肯定会遇到意想不到的行为,除非您真的知道你在做什么。

那么,prepareGeometryChangeupdateGeometry 对于 QGraphicsWidget 来说完全没有必要,并且使用项目几何形状调整小部件的大小绝对是 错误的 有两个原因:第一最重要的是,它是由图形布局来管理内容大小的,然后你正在使用 scene 坐标,并且由于你正在使用缩放,这些坐标将不正确,因为它们应该是在小部件的坐标中转换。

由于场景矩形不断变化,使用 QSizeGrip 是不可行的(我不得不说,如果与内容的交互式调整大小一起完成,这并不总是一个好主意),您可以使用一个简单的 QGraphicsPathItem它,并以此作为调整大小的参考,这比连续移动多边形并绘制要简单得多。

class SizeGrip(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent):
        super().__init__(parent)
        path = QtGui.QPainterPath()
        path.moveTo(0, 10)
        path.lineTo(10, 10)
        path.lineTo(10, 0)
        path.closeSubpath()
        self.setPath(path)
        self.setPen(QtGui.QPen(Qt.white))
        self.setBrush(QtGui.QBrush(Qt.white))
        self.setCursor(Qt.SizeFDiagCursor)


class GraphicsFrame(QtWidgets.QGraphicsItem):

    def __init__(self, *args, **kwargs):
        super(GraphicsFrame, self).__init__()

        x, y, w, h = args
        self.setPos(x, y)

        self.setAcceptHoverEvents(True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        self.container = Container()

        self.proxy = QtWidgets.QGraphicsProxyWidget(self)
        self.proxy.setWidget(self.container)

        self.proxy.setMinimumSize(150, 150)
        self.proxy.setMaximumSize(400, 800)
        self.proxy.resize(w, h)

        self.contentLayout = QtWidgets.QFormLayout()
        self.contentLayout.setContentsMargins(10, 10, 20, 20)
        self.contentLayout.setSpacing(5)

        self.container.layout.addLayout(self.contentLayout)
        self.options = []

        self.sizeGrip = SizeGrip(self)
        self.mousePressPos = None

        self.proxy.geometryChanged.connect(self.resized)
        self.resized()

    def addOption(self, color=Qt.white, lbl=None, widget=None):
        self.insertOption(-1, lbl, widget, color)

    def insertOption(self, index, lbl, widget, color=Qt.white):
        if index < 0:
            index = self.contentLayout.count()
        self.contentLayout.addRow(lbl, widget)
        self.options.insert(index, (widget, color))

    def mousePressEvent(self, event):
        gripShape = self.sizeGrip.shape().translated(self.sizeGrip.pos())
        if event.button() == Qt.LeftButton and gripShape.contains(event.pos()):
            self.mousePressPos = event.pos()
        else:
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.mousePressPos:
            delta = event.pos() - self.mousePressPos
            geo = self.proxy.geometry()
            bottomRight = geo.bottomRight()
            geo.setBottomRight(bottomRight + delta)
            self.proxy.setGeometry(geo)
            diff = self.proxy.geometry().bottomRight() - bottomRight
            if diff.x():
                self.mousePressPos.setX(event.pos().x())
            if diff.y():
                self.mousePressPos.setY(event.pos().y())
        else:
            super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        self.mousePressPos = None
        super().mouseReleaseEvent(event)

    def resized(self):
        rect = self.boundingRect()
        self.sizeGrip.setPos(rect.bottomRight() + QtCore.QPointF(-20, -20))

    def boundingRect(self):
        return self.proxy.boundingRect().adjusted(-11, -11, 11, 11)

    def paint(self, painter, option, widget):
        painter.save()
        painter.setBrush(QBrush(QColor(37, 181, 247)))
        painter.drawRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
        painter.restore()

请注意,在显示视图之前使用 fitInView() 并不是一个好主意,尤其是在使用代理小部件和布局时。