itemChange() problem
-
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 drag 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. 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. What is the recommended approach to do this right?
Using mouseMoveEvent towards the same end works very well, by the way. It's just that this fails when I select a group of items, because then the action only applies to the item under the mouse cursor.
-
Hi and welcome to devnet,
Not a direct answer but did you took a look at the Colliding Mice Example ? It shows a way to do collision detection.
Hope it helps
-
@Oatmeal
Please bear in mind that I know nothing aboutQGraphicsScene
, but....Clearly from what you say you observe it's "too late" by the time the
itemChange
event handler is called --- I presume Qt has already moved the item, and returning a new position works but causes a flicker-move-redraw. Correct?If so, it seems to me you'll need to handle the situation earlier, before the undesired move has happened. Can you instead intercept/handle the "drag" event, calculate the fact that it would move the rectangle to an undesired position, and deal with there? (Assumes that comes before the item is actually moved.)
Else you'll have to handle it at the
MouseMoveEvent
stage. And I think https://stackoverflow.com/a/13102984/489865 might give you enough idea to alter for what you need? Also perhaps look at https://stackoverflow.com/a/4161542/489865 ? -
I do something similar (although in C++), and it works for me. The only difference I see between our approaches is that, instead of directly returning the corrected value, I call the base class itemChange method with it.
-
@JNBarchan
Thanks for your suggestions. Yes, you are correct. And yes, I would like to handle the situation before the actual move has happened. And it does work like a charm when I handle the mouseEvent. The problem with that, though, is that when I select two items and drag them, mouseEvent() is only called for the one that actually is under the mouse cursor. So that one gets constrained, but the other one still can happily move wherever it wants.I took this this restraining method from the QT documentation (http://doc.qt.io/qt-4.8/qgraphicsitem.html#itemChange). I'm now wondering if it has something to do with the fact that I'm using Python, because it is inherently slower than C++.
-
@Oatmeal
Although I don't know what your exact code is, I should be most surprised if it were any noticeably slower than the corresponding C++. Python is not bad at speed, but in any case a few lines of code at the Python side is neither here nor there: the time will be spent in the Qt code it calls/is called from in a UI context. -
but I admit I didn't do a good job at getting the web page to display it well.
:) When posting a block of code here, use the
</>
"toolbar" button to cause to surround the whole code. It puts in:
```
(triple-backtick) markers on a line of their own at the start & end of the whole block, and does not rely on you indenting each line with spaces! -
Come on, guys, I wouldn't have thought that I have discovered an unsolvable problem with Qt or that I am trying to do something so far out of the ordinary. I'm sure that if somebody versed in Qt threw the above code in an editor and ran it, they would find out in the blink of an eye what I am doing wrong.
-
@Oatmeal I guess you already solved your issue since 9 months passed but I experienced same issue today and solved it eventually so I want to share it anyway. Instead of using below code
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
using below code did the trick for me
setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem::ItemSendsGeometryChanges);