Search code examples
pythonpyqtqgraphicsrectitem

QGraphicsItem interactive resize for rectangles


I am making an application where the user can draw rectangles, circles and polygons on a scene. These items can then be selected, deleted, moved around, etc. The code below shows what I have done to achieve this:

class Rectangle(QGraphicsRectItem):

    def __init__(self, x, y, w, h):
        super(Rectangle, self).__init__(0, 0, w, h)
        super().setPen(QPen(Qt.red, 2))
        super().setFlag(QGraphicsItem.ItemIsSelectable)
        super().setFlag(QGraphicsItem.ItemIsMovable)
        super().setFlag(QGraphicsItem.ItemIsFocusable)
        super().setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        super().setFlag(QGraphicsItem.ItemSendsScenePositionChanges)
        super().setPos(QPointF(x, y))

    def mouseMoveEvent(self, e):
        x = e.pos().x()
        y = e.pos().y()
        if e.buttons() == Qt.LeftButton:
            super().mouseMoveEvent(e)
        if e.buttons() == Qt.RightButton:
            super().setRect(QRectF(0, 0, x, y))

    def itemChange(self, change, val):
        if change == QGraphicsItem.ItemPositionChange:
            return QPointF(val.x(), val.y())
        return val

Working code for circle:

class Circle(QGraphicsEllipseItem):

    def __init__(self, x, y, w, h):
        super(Circle, self).__init__(0, 0, w, h)
        super().setPen(QPen(Qt.darkBlue, 2))
        super().setFlag(QGraphicsItem.ItemIsSelectable)
        super().setFlag(QGraphicsItem.ItemIsMovable)
        super().setFlag(QGraphicsItem.ItemIsFocusable)
        super().setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        super().setFlag(QGraphicsItem.ItemSendsScenePositionChanges)
        super().setPos(QPointF(x - w / 2 - 4, y - h / 2 - 4))

    def mouseMoveEvent(self, e):
        x = e.pos().x()
        y = e.pos().y()
        print(x, y)
        if e.buttons() == Qt.LeftButton:
            super().mouseMoveEvent(e)
        if e.buttons() == Qt.RightButton:
            super().setRect(QRectF(0, 0, x, y))

    def itemChange(self, change, val):
        if change == QGraphicsItem.ItemPositionChange:
            return QPointF(val.x(), val.y())
        return val

The ellipse I have has exactly the same code. When I resize the ellipse 'negatively' i.e. above the upper-left point it will resize accordingly. However the rectangle just disappears, it might leave a small trace of red but it just doesn't draw the way it's supposed to. Why is this different for the rectangle? How can I solve it?

Most solutions I see on stack overflow seem like overkill and way too much code for only a small implementation.


Solution

  • The rectangle that expects setRect() must be valid, for example QRectF(0, 0, 100, 100) is valid, but QRectF(100, 100, 0, 0) is not valid, so the solution is to make it valid with normalized()

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Rectangle(QtWidgets.QGraphicsRectItem):
        def __init__(self, x, y, w, h):
            super(Rectangle, self).__init__(0, 0, w, h)
            self.setPen(QtGui.QPen(QtCore.Qt.red, 2))
            self.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable
                | QtWidgets.QGraphicsItem.ItemIsMovable
                | QtWidgets.QGraphicsItem.ItemIsFocusable
                | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
                | QtWidgets.QGraphicsItem.ItemSendsScenePositionChanges)
            self.setPos(QtCore.QPointF(x, y))
    
        def mouseMoveEvent(self, e):
            if e.buttons() & QtCore.Qt.LeftButton:
                super(Rectangle, self).mouseMoveEvent(e)
            if e.buttons() & QtCore.Qt.RightButton:
                self.setRect(QtCore.QRectF(QtCore.QPoint(), e.pos()).normalized())
    
    
    if __name__ == '__main__':
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        scene = QtWidgets.QGraphicsScene()
        view = QtWidgets.QGraphicsView(scene)
        scene.addItem(Rectangle(0, 0, 100, 100))
        view.show()
        sys.exit(app.exec_())
    

    In the case of QGraphicsEllipseItem normalizes the rectangle so you do not see that problem.