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

Posted

技术标签:

【中文标题】在插入小部件时重新调整自定义 QGraphicsWIdget 的大小【英文标题】:Readjust the Custom QGraphicsWIdget's size on inserting widget 【发布时间】:2020-12-29 14:18:45 【问题描述】:

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

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

这是图片。

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

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('Containerbackground: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()

【问题讨论】:

【参考方案1】:

正如已经不止一次向您建议的那样,如果您只使用 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()之前并不是一个好主意,尤其是在使用代理小部件和布局时。

【讨论】:

嗨,首先,非常感谢。你知道我如何可以删除已添加的边距。我尝试将代理小部件边距设置为 0,但没有成功。 你指的边距是多少? Proxywidget.geometry()self.boundingRect() 相差 22。self.boundingRec() 宽度和高度是 22 倍 好的没关系找到它。只需将 return self.proxy.boundingRect().adjusted(-11, -11, 11, 11) 更改为 return self.proxy.boundingRect().adjusted(0, 0, 0, 0) 即可。但这会导致任何问题吗? 我根据您之前的代码添加了该边距,该代码在小部件周围使用了大边框。这不是问题,直到它是:边界矩形定义(在项目坐标中)允许绘画的矩形,因此它应该始终等于或大于内容。如果你想在孩子周围(和之外)画一个边框,它必须比他们的矩形大。如果您使用 (0, 0, 0, 0),adjusted 将完全无用(阅读 QRect 和 QRectF 文档)。

以上是关于在插入小部件时重新调整自定义 QGraphicsWIdget 的大小的主要内容,如果未能解决你的问题,请参考以下文章

带有媒体上传按钮的自定义Wordpress窗口小部件,在每个窗口小部件上插入相同的图像

隐藏子小部件时,QGridLayout 未调整大小或重新绘制

如何在无需重新定义整个表单的情况下使用带有通用 UpdateView 的自定义小部件?

使 QListWidget 调整其项目的大小以填充空间

更改 Qt 布局中的调整大小行为

Qt 在另一个小部件中调整小部件的大小