Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
128 views
in Technique[技术] by (71.8m points)

python - Readjust the Custom QGraphicsWIdget's size on inserting widget

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.

enter image description here

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...