Proper way to remove connections from a QGraphicsScene
-
Problems removing connections to QGraphicsScene.
I have a a class indirectly based on QGraphicsModel and another based on QGraphicsScene. In between is QtNodes.
QtNodes::BasicGraphicsScene contains a bunch of connect statements, like this one:
connect(&_graphModel, &AbstractGraphModel::nodeUpdated, this, &BasicGraphicsScene::onNodeUpdated);
There is no corresponding disconnect.
At some point, I am deleting the old scene and replacing it with a new one. The next time that nodeUpdated is emitted, OnNodeUpdated is called for the DELETED old scene. I'm trying to force a disconnect. I unfortunately am not free to modify the QtNodes code. I added code in my scene class that looks like this:
cSI_QTN_GRAPHICS_SCENE::~cSI_QTN_GRAPHICS_SCENE() { auto qItems = items(); for (QGraphicsItem* pItem : qItems) { removeItem(pItem); delete pItem; } disconnect(this, 0, 0, 0); disconnect(dynamic_cast<QtNodes::DataFlowGraphicsScene*>(this), 0, 0, 0); auto theGraphModel = dynamic_cast<cSI_QTN_MODEL*>(&graphModel()); disconnect(theGraphModel, &QtNodes::AbstractGraphModel::nodeUpdated, this, &QtNodes::BasicGraphicsScene::onNodeUpdated); }
I found comments on line that said that one had to delete the items. Didn't think so, but I tried it.
The first disconnect was to try and disconnect everything.
The second was another attempt at that. Again, didn't think it would work, but I tried it anyways.
And the last one is a direct disconnect of the connection.Still, whenever nodeUpdated is emitted, onNodeUpdated is called for the old, deleted scene.
This destructor, emit, and onNodeUpdated are all happening in the same thread.
What is the proper way to disconnect that connection?
Thanks
-
https://doc.qt.io/qt-6/qobject.html#disconnect-2
A signal-slot connection is removed when either of the objects involved are destroyed.
Explicitly disconnecting a slot for a destroyed object should not be necessary. Is the code using a lambda connection without a context object?
-
I read that the automatic destruction doesn't work for QGraphicsScene because it doesn't derive from QOBject, but I just checked and it does. derive from QObject.
So what could cause the behavior that I am seeing? I'm definitely getting a signal to a deleted scene.
-
I read that the automatic destruction doesn't work for QGraphicsScene because it doesn't derive from QOBject, but I just checked and it does. derive from QObject.
So what could cause the behavior that I am seeing? I'm definitely getting a signal to a deleted scene.
@james-b-s
As @jeremy_k says, so long as yourBasicGraphicsScene
really is derived fromQGraphicsScene/QObject
you should not have to do any of the stuff you have in your~cSI_QTN_GRAPHICS_SCENE()
code. Nor should you be seeing the old call. Why don't you set just this up in a standalone test of your own, doing a scene replacement on perhaps a timer, and verify behaviour?Also suggest search code for
nodeUpdated
, as he says if there is a lambda connection on that for slot with no context object (likethis
) that would still exist and cause this behaviour. -
The code that does the connect looks like this:
connect(&_graphModel,
&AbstractGraphModel::nodeUpdated,
this,
&BasicGraphicsScene::onNodeUpdated);I don't see any lambda involved
@james-b-s
Indeed there is not in there. The question is whether they might be elsewhere in code, hence the suggestion to search.Btw, attach a slot to the scene's
destroyed()
signal and confirm you receive that, before your signal. -
It appears that at least one of the connections that is having problems does involve a lambda. I found this in the code for the scene (derived from QGraphicsScene)
connect(&_graphModel, &DataFlowGraphModel::inPortDataWasSet, [this](NodeId const nodeId, PortType const, PortIndex const) { onNodeUpdated(nodeId); });
This is in QtNodes. I am not able to modify this code.
The sequence of events is
- I create a new scene
- I replace the scene held by the view with the new scene
- I verify that the view has the new scene
- I delete the old scene
- I start doing stuff, trigging the signal posted above, the signal is received by the old deleted scene.
I overrode disconnectNotify and verified that the connection is not getting deleted.
This is Qt 5.15. Without modifying the code in QtNodes, is there anyway for me to force the signal to be disconnected? According to the documentation, the signal should be disconnected when the destruction is complete, but that is not happening. I did find some information online that the signal won't be disconnected because the receiver has been destroyed and is now invalid. So the question remains, is there any way for me to disconnect that connection?
-
It appears that at least one of the connections that is having problems does involve a lambda. I found this in the code for the scene (derived from QGraphicsScene)
connect(&_graphModel, &DataFlowGraphModel::inPortDataWasSet, [this](NodeId const nodeId, PortType const, PortIndex const) { onNodeUpdated(nodeId); });
This is in QtNodes. I am not able to modify this code.
The sequence of events is
- I create a new scene
- I replace the scene held by the view with the new scene
- I verify that the view has the new scene
- I delete the old scene
- I start doing stuff, trigging the signal posted above, the signal is received by the old deleted scene.
I overrode disconnectNotify and verified that the connection is not getting deleted.
This is Qt 5.15. Without modifying the code in QtNodes, is there anyway for me to force the signal to be disconnected? According to the documentation, the signal should be disconnected when the destruction is complete, but that is not happening. I did find some information online that the signal won't be disconnected because the receiver has been destroyed and is now invalid. So the question remains, is there any way for me to disconnect that connection?
@james-b-s
As we suspected :) Thatconnect()
has only 3 parameters, right? Even though it uses[this]
for the context I don't think that makes the compiled code treat as destroy onthis
destruction? (At a guess your connection persists till_graphModel
is destroyed?)Change it to take
this
as 3rd parameter for slot object in standard 4 parameterconnect()
:connect(&_graphModel, &DataFlowGraphModel::inPortDataWasSet, this, // slot object, destroy connection when `this` object destroyed [this](NodeId const nodeId, PortType const, PortIndex const) { onNodeUpdated(nodeId); });
Does that indeed resolve your case?
-
For Qt6.7 and up: https://doc.qt.io/qt-6/qobject.html#QT_NO_CONTEXTLESS_CONNECT
-
@james-b-s
Then I think you have a problem! I believe to disconnect a lambda explicitly (if you cannot introduce a slot object) you need to store the result of theconnect()
and pass that todisconnect()
. But presumably you cannot do that either if the code you show is untouchable third-party?P.S.
If you're interested in a discussion on this slot-lambda-functor-without-slot-object destruction problem read through https://interest.qt-project.narkive.com/AiSB9wkP/who-disconnects-lambda-expression-slots from when it was raised 12 years ago :)