Using Signals in QGraphicsItem
-
Hi there,
I'm trying to create a scene with 4 lines than can be moved around, but constrained by the position of the others.
So I'm trying to use signals so that on mouseMoveEvent, I can emit that signal and the scene will be connected to it and move the line accordingly.
The thing is, I cannot create a signal for a QGraphicsLineItem...Is there a way to do what I'm trying to do through Qt Signals ? Or should I just use good-old callbacks ?
Thanks for your help.
-
@adrien-lsh
I think it is to do with the initialisation, requiring both inherited classes to be initialised. You were only doing so for theQGraphicsLineItem
, not initialising theQObject
. I find the following does work without crashing:class CroppingLine(QObject, QGraphicsLineItem): moved = Signal(object, QPointF) def __init__(self, x1: float, y1: float, x2: float, y2: float): #super(QGraphicsLineItem, self).__init__(x1, y1, x2, y2) QObject.__init__(self) QGraphicsLineItem.__init__(self, x1, y1, x2, y2)
There may be other ways of doing this with
super()
and/or not swapping the inheritance order, I just know this works fine. -
@adrien-lsh
QGraphicsItem
s are lightweight: they do not inheritQObject
so cannot participate in signals. If you want signals you must either start from QGraphicsObject or subclass e.g. yourQGraphicsLineItem
to add inheritance fromQObject
. Or do your signalling from elsewhere. -
Actually you can implement your own Signal class, it's pretty trivial, here is a quick and dirty example :
class Signal: def __init__(s): s._handlers_ = [] def connect(s, func): if func not in s._handlers_: s._handlers_.append(func) def emit(s,*args,**kargs): for func in s._handlers_: if func(*args, **kargs): break
From the class that you want emit a non-existing signal, create an instance of the
Signal
class as an instance attribute.
And when you want to emit, just callSignal
instanceemit()
method.It's pretty convenient. However, it does require you sublass a
QGraphicsItem
, aQGraphicsLineItem
in your case. -
Hello, I used signals that are emited from QGraphicsLineItems but not directly, by using their QGraphicsScene (indeed a subclassed one) that inherits QObject. Things like that :
void PieceLigneItem::mousePressEvent (QGraphicsSceneMouseEvent *event) { qDebug() << this->ligne->id1 << this->ligne->id2; if (this->ligne->nb == 1) { emit sceneD->peutColorierFace(this->ligne->id1, this->ligne->id2); } else { emit sceneD->pieceEnleveFaces(this->ligne->id1, this->ligne->id2); } } void PieceLigneItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { QPen p = this->pen(); p.setColor(Qt::yellow); emit sceneD->ligneHoverOn(this->ligne->id1, this->ligne->id2); setPen(p); }
(sorry, I use C++, not Python)
-
@Nlight said in Using Signals in QGraphicsItem:
Actually you can implement your own Signal class, it's pretty trivial
Your code is fine per se, but that is not using Qt signals at all. It lacks all sorts of stuff provided by Qt signals, it's just a queue a arbitrary functions to call. If you're going to subclass a
QGraphicsItem
you can just addQObject
to inherit from and use Qt signals. -
Thank you all for your answers
I think I'll go with the easier implementation and just add the QObject inheritance to my custom QGraphicsLineItem class. -
-
Ok I'm having another problem now :/ The program crashes when I try to move the lines (without any information in the console)
class CroppingLine(QGraphicsLineItem, QObject): moved = Signal(object, QPointF) def __init__(self, x1: float, y1: float, x2: float, y2: float, is_vertical: bool): super().__init__(x1, y1, x2, y2) self.is_vertical = is_vertical self.setPen(QPen(Qt.GlobalColor.black, 2)) self.setCursor(Qt.CursorShape.SizeVerCursor if is_vertical else Qt.CursorShape.SizeHorCursor) self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) def mouseMoveEvent(self, event): super().mouseMoveEvent(event) self.moved.emit(self, self.scenePos())
Is there something I did wrong ?
-
@adrien-lsh
What happens if you do not attach a slot to the signal?
What happens if you do not emit the signal?
What happens if you define and emit a signal without any parameters? -
@JonB
I tried all of that already, when the signal is emitted (even without or with different parameters) it crashes: the line does not move, it waits a bit, then the window closes.
It crashes on the Signal.emit().btw sorry I cannot respond faster I have the 10m new guy cooldown
-
@adrien-lsh
So you are claiming if you inherit fromQObject
, define a signal and emit it without a slot attached it crashes?Try swapping the inheritance order to make
QObject
the first class inherited from (i.e.(QObject, QGraphicsLineItem)
. This would be required for moc from C++, I don't know if it might be required from Python. I am (then) also slightly unsure about plainsuper()
calls from Python, I don't know if you need to qualify that to indicate which of the two super classes to use.You have not said what version of Qt you are using and whether PySide or PyQt. If you produce a minimal, standalone example which I can copy and paste I will test here.
-
@JonB
Yes that's what is happening. I am using PySide6 and here is an example:from PySide6.QtCore import QObject, QPointF, Signal from PySide6.QtGui import QPen, Qt from PySide6.QtWidgets import QApplication, QGraphicsItem, QGraphicsView, QGraphicsScene, QGraphicsLineItem class GraphicsView(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) scene = QGraphicsScene() self.line = CroppingLine(0, 0, 100, 100) scene.addItem(self.line) self.setScene(scene) class CroppingLine(QGraphicsLineItem, QObject): moved = Signal(object, QPointF) def __init__(self, x1: float, y1: float, x2: float, y2: float): super().__init__(x1, y1, x2, y2) self.setPen(QPen(Qt.GlobalColor.blue, 3)) self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) def mouseMoveEvent(self, event): super().mouseMoveEvent(event) self.moved.emit(self, self.scenePos()) if __name__ == '__main__': import sys app = QApplication(sys.argv) window = GraphicsView() window.show() sys.exit(app.exec())
About the swap of the superclasses, I dont think it would work because the
super().__init__()
would not callQGraphicsLineItem
init function butQObject
's. -
-
@adrien-lsh
Before you make me try this, I did ask you putQObject
first, that is at least required using C++/moc so I'd like you to test it. As I said, you should look up howsuper()
works in Python, I think you can call something likesuper(QGraphicsLineItem).__init__()
(actually it might besuper(QGraphicsLineItem, self).__init__()
?) if you need to, but you are the Python programmer! -
@JonB
Nope it does not work withsuper(QGraphicsLineItem, self).__init__(...)
because Python is trying to call the super class ofQGraphicsLineItem
which isQGraphicsItem
and my class does not directly inherit from it so it doesnt work.I did achieve the inheritance swap using
super(QObject, self).__init__(...)
tho and my line displays correctly but it still crashes :/ -
@adrien-lsh
Since you seem to have provided a nice, standalone, complete example I can copy and paste --- unlike so many other people --- I will give it a play now.... -
@adrien-lsh
Yeah, that's easier said than done! It's so thin, and I can't tell when I have actually grabbed it or not! Anyway I have reproed now, it does aSIGSEGV
from0x00007ffff711fc14 in PySide::SignalManager::emitSignal(QObject*, char const*, _object*) () from /home/jon/.venv/pyside6/lib/python3.12/site-packages/PySide6/libpyside6.abi3.so.6.7 (gdb) bt #0 0x00007ffff711fc14 in PySide::SignalManager::emitSignal(QObject*, char const*, _object*) () at /home/jon/.venv/pyside6/lib/python3.12/site-packages/PySide6/libpyside6.abi3.so.6.7
Looking into that now...
-
@adrien-lsh
I think it is to do with the initialisation, requiring both inherited classes to be initialised. You were only doing so for theQGraphicsLineItem
, not initialising theQObject
. I find the following does work without crashing:class CroppingLine(QObject, QGraphicsLineItem): moved = Signal(object, QPointF) def __init__(self, x1: float, y1: float, x2: float, y2: float): #super(QGraphicsLineItem, self).__init__(x1, y1, x2, y2) QObject.__init__(self) QGraphicsLineItem.__init__(self, x1, y1, x2, y2)
There may be other ways of doing this with
super()
and/or not swapping the inheritance order, I just know this works fine. -