1

I am trying unsucessfully to implement a resizable rectangle QGraphicsItem in the pyside6 QGraphics framework by inheriting from QGraphicsRectItem. In the ResizableRectItem class, I create resize handles that are themselves QGraphicsRectItems and children of the ResizableRectItem, which allows them to be translated together with their parent item.

The problem I encounter and can't seem to solve, is that I want these handles to only appear when the Rectangle is selected with the mouse. This works fine (by overriding the itemChange method), but the problem is that the handles can only be moved when the Rectangle is NOT selected. While the rectangle is selected, they cannot be moved.

Moreover, I don't know how, once I get the handles to move, I can propagate the movement of a specific handle to the parent rectangle, so as to resize its shape accordingly. I initially thought about using signals/slots, however this mechanism is unavailable since QGraphicsItem does not inherit from QObject. I read that there is also a QGraphicsObject to provide signals/slots, but I suspect there might be a more elegant solution which I'm not seeing at the moment. I would be glad if someone could help me out here. I have googled this question extensively and not found a satisfactory answer. Thank you in advance!

import sys

from PySide6.QtCore import Qt
from PySide6.QtGui import QPen, QColor, QBrush
from PySide6.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsItem


class ResizableRectItem(QGraphicsRectItem):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setPen(QPen(Qt.black))
        self.setBrush(QBrush(Qt.gray))
        self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)

        self.handleSize = 8
        self.handles = {}
        self.directions = ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
        self.createHandles()

    def createHandles(self):
        rect = self.rect()
        pen = QPen(QColor(0, 0, 0))
        for direction in self.directions:
            handle = QGraphicsRectItem(-self.handleSize/2, -self.handleSize/2, self.handleSize, self.handleSize, self)
            handle.setPen(pen)
            handle.setFlags(QGraphicsItem.ItemIsMovable)
            handle.setVisible(False)
            # Use getattr to replace this calls like this: rect.upperLeft()
            handle.setPos(getattr(rect, direction)())
            self.handles[direction] = handle

    def itemChange(self, change, value):
        # Intercept selection event to change visibility of handles
        if change == QGraphicsItem.GraphicsItemChange.ItemSelectedChange:
            for handle in self.handles.values():
                handle.setVisible(bool(value))
        # Pass to original method to handle all other changes
        return super().itemChange(change, value)


class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setGeometry(500, 500, 500, 500)
        self.view = QGraphicsView()
        self.scene = QGraphicsScene()
        self.view.setScene(self.scene)
        rectItem = ResizableRectItem(100, 100, 200, 200)
        self.scene.addItem(rectItem)

        self.setCentralWidget(self.view)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
sunnytown
  • 1,844
  • 1
  • 6
  • 13
  • Within your question relies an important aspect. QGraphicsItem can show contents that is not actually shown at their *position*. The default position of an item is always at `0, 0` (related to its parent, and based on the [Graphics View Coordinate System](https://doc.qt.io/qt-5/graphicsview.html#the-graphics-view-coordinate-system)), meaning that if you create a `QGraphicsRectItem(100, 100, 200, 200)` its position will be `0, 0` and its rectangle will be shown at `100, 100`. What do you expect from moving the left/top/right handles? Should they change the item's position or just its rect? – musicamante Jul 26 '23 at 02:39
  • The handles should change also the position, since the center of the Item will inevitably move when resizing. The resizing should be just as in a powerpoint shape. – sunnytown Jul 26 '23 at 07:06
  • Focus on a single problem. Anyway, for the first problem related on selected items, you can return ```False``` in the ```itemChange()``` if the ```value``` is ```True```. And for the second problem, you can use the ```mouseXxxEvent()``` methods of the ```QGraphicsItem```. – relent95 Jul 26 '23 at 07:45
  • What would be accomplished by returning `False` in the `itemChange` method? – sunnytown Jul 26 '23 at 08:09
  • @sunnytown I'm afraid you've not understood the point. It's obvious that resizing the rectangle will move its center, but, as said, you can have a rectangle *shown* at certain coordinates while having its item *placed* at others: doing `ResizableRectItem(100, 100, 200, 200)` gives you a rectangle *shown* at (100,100), but the item is placed at (0,0); you can also do `ResizableRectItem(0, 0, 200, 200)` and then `rectItem.setPos(100, 100)`, then you will have a similar *visual* result, but it will **not** be the same thing, and there is a *huge* difference between those ways of item placement. – musicamante Jul 26 '23 at 10:06
  • @relent95 Returning `False` for `ItemSelectedChange` is not a solution, as it will prevent the selection to be applied (thus making the item *not* selected both programmatically *and* visually); besides, "returning `False` if the `value` is `True`" is fundamentally the same thing as *always* returning `False`: since the value for that `GraphicsItemChange` is a boolean, it would be as pointless as doing `return False if value else False`, which is exactly the same as doing `return False`. – musicamante Jul 27 '23 at 03:09
  • @sunnytown I may have a possible answer for you, but you really need to clarify the aspect of top and left coordinates (whether they are item or contents based, because that aspect can change a lot of things). I strongly suggest you to read more about the [Graphics View Coordinate System](//doc.qt.io/qt-6/graphicsview.html#the-graphics-view-coordinate-system) and how the coordinates of each item (and their contents) are related to their parent(s), including the scene and the view. – musicamante Jul 27 '23 at 03:13
  • @musicamante, I suggested a partial solution of deselecting the selected item after showing markers(handles), which enables to move the markers. You are right for just returning ```False```. – relent95 Jul 27 '23 at 05:15

0 Answers0