1

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.

Athylus
  • 193
  • 1
  • 1
  • 10

1 Answers1

2

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks! That is indeed working. However, when I access the rectangle and ask for it's rect() it returns: QRectF(negative value, negative value, positive value, positive value). It does not seem to update the position and rect properly. – Athylus Oct 22 '18 at 12:36
  • 1
    @Athylus I do not understand you, give a clearer example, what are `negative value, negative value, positive value, positive value`? – eyllanesc Oct 22 '18 at 12:36
  • Say I have a shape at pos(100, 100) with rect(0,0,20, 20) and resize it negatively it will not update the position and rect properly. Say I move my mouse to 80,80 it will still say pos(100, 100) and rect(-20,-20, 20, 20). Why does it not update properly? – Athylus Oct 22 '18 at 13:04
  • 1
    @Athylus In QGraphicsView and QGraphicsScene there are several coordinate systems, the QGraphicsView that refers to the pixels, the QGraphicsScene which are the coordinates that establish the position, so you understand the above I give an example: let's say you are recording a scene with your camera , the QGraphicsView is the screen of the camera and the world is QGraphicsView. In addition to the above, each item has its own coordinate that is used to draw, for example in your case it is the shape of QRect – eyllanesc Oct 22 '18 at 13:09
  • 1
    @Athylus What you say is correct: as you resize the rectangle using setRect(), that is, with respect to the local coordinates of the item, not the scene, the position of the item will be the same, and then the rectangle will vary, this will be rect(- 20, -20, 20, 20), what do you think is weird? – eyllanesc Oct 22 '18 at 13:13
  • Ah I see. By calling sceneBoundingRect() I get the actual x, y, width and height in the QGraphicsScene. Thanks! – Athylus Oct 22 '18 at 13:23
  • 1
    @Athylus To understand everything about QGraphicsView and QGraphicsScene you should read at least the following: http://doc.qt.io/qt-5/graphicsview.html – eyllanesc Oct 22 '18 at 13:26
  • I will. Thanks for the link. – Athylus Oct 23 '18 at 12:52