Solved Advice on the 'Qt way' regarding swapping UIs.
-
I have a QML UI that, when a user clicks on a button, is to destroyed and replaced with a new UI as it is hosted in a QQuickView (I set source to nothing and clear the component cache.)
Now, the code to do this in Qt is simple and straightforward; however, I am wondering how to handle (in Qt) the issue that is sure to arise when a Qt component generates an onClick call, and that onClick call calls from Qml back into my C++ application code - where the QML should then be destroyed and replaced with new Qml.
Clearly, something bad will happen if this occurs synchronously (as the button that is calling into my C++ code is supposed to get wiped by my C++ code - so how can it return?)
Is there a method for my Qml to invoke my C++ code asynchronously, returning immediately and then the event management loop on the C++ side executes the code that erases the old Qml and loads the new?
Or is this what signals are for? Iirc (and this was a long time ago for me, as in 1996) signal were executed immediately - if signals work via messages in a message loop, is there a 'post the signal' variation rather than a 'wait for the signal to be handled' call?
Thanks - Hans
-
Perhaps a QTimer::singleShot() timer on the C++ side? Using a timer is a little ugly (you have to worry about the lifecycle of the timer as it can introduce a race condition; however, I would guess that if I set the time to 0, that'll be similar to a Sleep(0) where the C++ side will return immediately, and asap the timer will fire.
-
I think the least messy approach may be to have my Qml code call into C++ and say "switch UI to 'some ui'" and that C++ method Post a QEvent to the application message queue saying "switch UI to 'some ui'" so that the C++ method can immediately return and idle time processing will yield to the QEvent eventually and I can switch UIs safely at that point. Thoughts?
-
The ::postEvent route worked fine for me.
QmlRequestUIEvent* pEvent = new QmlRequestUIEvent( EVENTID_QML_REQUEST, in_qsQmlMessage ); m_pGuiApplication->postEvent( this, pEvent );
-
I created a new event type with an enum ID of ( QEvent::User + 1 )
-
Then created a subclass of QEvent (because I wanted to pass data with the event)
-
Then the Qml code calls my C++ class and that C++ instance method posts the Event to the application's event queue, and returns control back to Qml.
-
My C++ class overrides the QObject::event method and catches the event, identifies it as a QmlRequestUIEvent type, and then calls the code that wipes the Qml UI.
Yay! Qml driven Qml destruction ;)
-
-
@VRHans
I don't develop with QML, butdeleteLater
doesn't do you good? I would think it should. I'd try that instead of deriving a custom event type. Suppose in your C++ you switch your UIs and just calldeleteLater
on the old one. When the control returns to the event loop and the differed delete event is processed the old UI element will be deleted. -
@kshegunov said:
deleteLater
Thanks for the suggestion, but I don't do that because I'm clearing out the QQuickView and clearing the component cache. Using deleteLater I can't clear the component cache.
-
@VRHans said:
QQuickView and clearing the component cache.
I'm sorry I don't follow. Calling QQmlEngine::trimComponentCache after the deletion happened should take care of that, shouldn't it? So I get
deleteLater
wouldn't work directly but basically the same idea can be applied by scheduling a deferred switching of UI through aQ_INVOKABLE
call, or am I missing something?What I'd envision is when you receive your event you queue an invokable function call (in some QObject of your choosing) with the new and old UI's (or a workable set of arguments) and then delete the old UI, trim the cache and set the new UI.
-
@kshegunov said:
QQmlEngine::trimComponentCache
Yes, but I can't call trim until the delete happens because the objects will still be considered still in use, so I'd have to hook the signal destroyed() for an arbitrary number of objects in the Qml hierarchy (since I can't trim until they are all gone.)
Using postEvent is just much cleaner for me. Heck, the only reason I subclass QEvent is because I want to pass extra data.
class MyCustomEvent : public QEvent { public: QString m_sMyCustomData; MyCustomEvent( QEvent::Type in_eType, QString in_qsData ) : QEvent( in_eType ) { m_sMyCustomData = in_qsData; } };
And when that event processes, cleaning everything up directly.
I'm not sure what event you were referring to when you said "...when you receive your event." When the event I described occurs, it's already safe to directly destroy all the Qml in the QQuickView via setSource.
-
Yes, but I can't call trim until the delete happens because the objects will still be considered still in use, so I'd have to hook the signal destroyed() for an arbitrary number of objects in the Qml hierarchy
Actually only the root object of the hierarchy is of interest, the children will be deleted automatically. Ultimately, what I mean is this:
QObject * rootObject; //< This is your root object QObject * objectHandlingCleanup; //< This is where you manage your trim/cleanup/switching of UI QObject::connect(rootObject, SIGNAL(destroyed(QObject *)), objectHandlingCleanup, SLOT(switchUi()), Qt::QueuedConnection); //< Notice the connection is queued through the event loop void MyClassHandlingCleanup::switchUi() { //< Do trim components here //< Set new root/UI here }
Using postEvent is just much cleaner for me. Heck, the only reason I subclass QEvent is because I want to pass extra data.
That's fine, I'm only trying to suggest an alternative approach.
I'm not sure what event you were referring to when you said "...when you receive your event."
I meant this:
"Qt component generates an onClick call, and that onClick call calls from Qml back into my C++ application code"Beside the snippet that's posted above, you could use
QMetaObject::invokeMethod
in a similar fashion. Suppose you have a click event in C++ (or a slot, or regular function, that's reacting to the click from QML):void MyClassHandlingCleanup::onClick() { QString myCustomData = data; QMetaObject::invokeMethod(this, "switchUi", Qt::QueuedConnection, Q_ARG(QString, myCustomData)); //< Again invocation is queued through the event loop } void MyClassHandlingCleanup::switchUi(QString customData) { //< Delete the object here with the delete operator //< Trim //< Insert new Ui/Component w/e }
For the last snippet to work, the
switchUi
method should either be declared as invokable with theQ_INVOKABLE
macro like this:class MyClassHandlingCleanup : QObject { Q_OBJECT public: Q_INVOKABLE void switchUi(QString); };
or be declared as a slot.
I hope that clears it up.Kind regards.
-
Thanks for the suggestions :).
I find:
QMetaObject::invokeMethod(this, "switchUi", Qt::QueuedConnection...
interesting too. Appreciated.
Cheers!