Reputation:
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)
Upvotes: 1
Views: 403
Reputation: 333
By reimplementing the mouseMoveEvent method, items can be easily restricted from leaving the sceneRect:
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
# 1. Finding the maximum movements that can be made before leaving the scene's
# bounding rectangle:
rect = self.mapRectToScene(self.boundingRect())
max_left_move = self.scene().sceneRect().left() - min(rect.left(), rect.right())
max_right_move = self.scene().sceneRect().right() - max(rect.left(), rect.right())
max_up_move = self.scene().sceneRect().top() - min(rect.top(), rect.bottom())
max_down_move = self.scene().sceneRect().bottom() - max(rect.top(), rect.bottom())
# 2. Calculating the initial vector of movement
move = event.pos() - event.buttonDownPos(Qt.LeftButton)
# 3. Correcting the movement vector if it causes the item to leave the area:
move.setX(np.clip(move.x(), max_left_move, max_right_move))
move.setY(np.clip(move.y(), max_up_move, max_down_move))
# 4. Moving the item
self.moveBy(move.x(), move.y())
Since I needed to restrict items with transformations, the method that @musicamante offered was not appropriate for my purpose.
Upvotes: 0
Reputation: 48231
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:
sceneTransform()
to get the actual bounding rect of the polygon;boundingRect()
of the item and adjust it by using half the pen width;(0, 0)
by default, specifying it again is pointless;shape()
is an existing (and quite important) function of all items, you shall not overwrite it;Upvotes: 2