I am using pyqt and Python 3. I want to prevent a QGraphicsRectItem to cross the horizontal axis (y=0) in a QGraphicsScene when dragged with the mouse. I am using the following code (employing height() because the rectangle is in the upper half of the screen). See below a full example of the code.
import sys
from PyQt4.QtCore import Qt, QPointF
from PyQt4.QtGui import QGraphicsRectItem, QGraphicsLineItem, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem
class MyRect(QGraphicsRectItem):
def __init__(self, w, h):
super().__init__(0, 0, w, h)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionChange:
if self.y() + self.rect().height() > 0:
return QPointF(self.x(), -self.rect().height())
return value
def main():
# Set up the framework.
app = QApplication(sys.argv)
gr_view = QGraphicsView()
scene = QGraphicsScene()
scene.setSceneRect(-100, -100, 200, 200)
gr_view.setScene(scene)
# Add an x-axis
x_axis = QGraphicsLineItem(-100, 0, 100, 0)
scene.addItem(x_axis)
# Add the restrained rect.
rect = MyRect(50, 50)
rect.setPos(-25, -100) # <--- not clear to me why I have to do this twice to get the
rect.setPos(-25, -100) # item positioned. I know it has to do with my itemChanged above...
scene.addItem(rect)
gr_view.fitInView(0, 0, 200, 200, Qt.KeepAspectRatio)
gr_view.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In principle this is working, but when I keep dragging the mouse below the horizontal axis (y=0), the rectangle flickers and jumps back and forth between the mouse position and its restrained position in the upper hemiplane while dragging. So it looks like the drag first moves it to the mouse cursor, and only then the position is adjusted retroactively. I would like the adjustment to happen before the item is moved (visibly) at all.
You use self.y() + self.rect().height() > 0
to test if the item is still above the y-axis. However, self.y()
refers to the old/current position. You should test on the new position with value.y()
instead.
So the method should be:
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionChange:
if value.y() + self.rect().height() > 0:
return QPointF(value.x(), -self.rect().height())
return super().itemChange(change, value) # Call super
Note that I return value.x()
if the test passes and call itemChange
of the super class if the test fails (just like the C++ example in the itemChange Qt documentation)