pythonpyside2

How to limit the area in QGraphicsScene where a custom QGraphicsItem can be moved?


I have a QGraphicsScene where I have QGraphicsItems. These items are movable and I can move them all over the QGraphicsScene but I would like to limit the area where these items can be moved. The sizes of the QGraphicsScene don't have to change. I would really appreciate if someone gave me an example of how to do it in python.

Here's what I have now

from PySide2.QtCore import QPointF
from PySide2.QtWidgets import QWidget, QVBoxLayout, QGraphicsView, \
    QGraphicsScene, QGraphicsPolygonItem, QApplication
from PySide2.QtGui import QPen, QColor, QBrush, QPolygonF


class Test(QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        self.resize(1000, 800)
        self.layout_ = QVBoxLayout()
        self.view_ = GraphicsView()

        self.layout_.addWidget(self.view_)
        self.setLayout(self.layout_)

class GraphicsView(QGraphicsView):
    def __init__(self):
        super(GraphicsView, self).__init__()
        self.scene_ = QGraphicsScene()
        self.polygon_creation = self.PolyCreation()

        self.scene_.setSceneRect(0, 0, 400, 400)
        self.setScene(self.scene_)

        self.polyCreator()

    def polyCreator(self):
        self.polygon_creation.poly()
        polygon = self.polygon_creation.polygon()
        new_poly = self.scene().addPolygon(polygon)
        new_poly.setBrush(QBrush(QColor("gray")))
        new_poly.setPen(QPen(QColor("gray")))
        new_poly.setFlag(QGraphicsPolygonItem.ItemIsSelectable)
        new_poly.setFlag(QGraphicsPolygonItem.ItemIsMovable)
        new_poly.setFlag(QGraphicsPolygonItem.ItemIsFocusable)
        new_poly.setPos(0, 0)

    class PolyCreation(QGraphicsPolygonItem):
        def __init__(self):
            super().__init__()
            self.setAcceptHoverEvents(True)

        def poly(self):
            self.poly_points = (QPointF(0, 0),
                                      QPointF(0, 50),
                                      QPointF(50, 50),
                                      QPointF(50, 0))

            self.shape = QPolygonF(self.poly_points)
            self.setPolygon(self.shape)


if __name__ == '__main__':
    app = QApplication([])
    win = Test()
    win.show()
    app.exec_()

I've also found an answer in cpp, but I can't understand it very well, so if someone could "translate" it in python that'd be great too. Here's the link restrict movable area of qgraphicsitem (Please check @Robert's answer)


Solution

  • The concept is to restrict the new position before it's finally applied.

    To achieve so, you need to also set the ItemSendsGeometryChanges flag and check for ItemPositionChange changes, then compare the item bounding rect with that of the scene, and eventually return a different position after correcting it.

    class PolyCreation(QGraphicsPolygonItem):
        def __init__(self):
            super().__init__(QPolygonF([
                QPointF(0, 0),
                QPointF(0, 50),
                QPointF(50, 50),
                QPointF(50, 0)
            ]))
            self.setBrush(QBrush(QColor("gray")))
            self.setPen(QPen(QColor("blue")))
            self.setFlags(
                self.ItemIsSelectable
                | self.ItemIsMovable
                | self.ItemIsFocusable
                | self.ItemSendsGeometryChanges
            )
            self.setAcceptHoverEvents(True)
    
        def itemChange(self, change, value):
            if change == self.ItemPositionChange and self.scene():
                br = self.polygon().boundingRect().translated(value)
                sceneRect = self.scene().sceneRect()
                if not sceneRect.contains(br):
                    if br.right() > sceneRect.right():
                        br.moveRight(sceneRect.right())
                    if br.x() < sceneRect.x():
                        br.moveLeft(sceneRect.x())
                    if br.bottom() > sceneRect.bottom():
                        br.moveBottom(sceneRect.bottom())
                    if br.y() < sceneRect.y():
                        br.moveTop(sceneRect.top())
                    return br.topLeft()
            return super().itemChange(change, value)
    
    
    class GraphicsView(QGraphicsView):
        def __init__(self):
            super(GraphicsView, self).__init__()
            self.scene_ = QGraphicsScene()
    
            self.scene_.setSceneRect(0, 0, 400, 400)
            self.setScene(self.scene_)
            self.scene_.addItem(PolyCreation())
    

    Notes: