Readjust the Custom QGraphicsWIdget's size on inserting widget

主宰稳场 提交于 2021-01-29 11:11:53

问题


I have created a custom QGraphicsWidget with the ability to resize the widget in the scene. I can also add predefined widgets such as buttons, labels, etc. to my custom widget. I now have two problems. The first being that the widget doesn't change the size (to re-adjust) upon inserting a new label or LineEdit widget as a result newly inserted widget stays out of the custom widget border.

The second problem is encountered when I try to change the setContentMargins of the QGraphicsLayout to something other than 0. For example QGraphicsLayout.setContentMargins(1, 1, 1, 20) will delay the cursor in the LineEdit widget.

Here is the image.

(Drag the grey triangle to change size)

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()

回答1:


As already suggested to you more than once, using a QGraphicsWidget with a QGraphicsLayout is not a good idea if you are only using it to embed a QGraphicsProxyWidget, as you will certainly have to face unexpected behavior when changing geometries unless you really know what you're doing.

Then, prepareGeometryChange and updateGeometry are completely unnecessary for QGraphicsWidget, and resizing the widget using the item geometries is absolutely wrong for two reasons: first of all, it's up the graphics layout to manage the content size, then you're using scene coordinates, and since you're using scaling, those coordinates will not be correct as they should be transformed in widget's coordinate.

Since using a QSizeGrip is not doable due to the continuously changing scene rect (which, I have to say, is not always a good idea if done along with interactive resizing of contents), you can use a simple QGraphicsPathItem for it, and use that as a reference for the resizing, which is far more simple than continuously move the polygon and draw it.

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()

Do note that using fitInView() before showing the view is not a good idea, especially if using proxy widgets and layouts.



来源:https://stackoverflow.com/questions/65493612/readjust-the-custom-qgraphicswidgets-size-on-inserting-widget

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!