Unsolved Convenient way to execute C++ code after QML has rerendered
-
Hi all,
In a QML application I have to update the view right after it has been rendered, and I'm looking for a simple snippet to achieve this.
To be precise:
- I have a custom
MyVTKModel {}
QML element that usesQQuickFramebufferObject
to render a 3D model. - Another QML element
Overlay {}
is drawn on top of this, but it depends on how the 3D model is rendered -- for instance, it should know how many pixels one millimeter in the scene is. - This means
Overlay{}
should update only afterMyVTKModel{}
has rendered.
In a controller in C++ I'm changing the camera and moving the object, and it would now be very convenient if I could update the overlay in the same function. I was hoping I could just do
QTimer::singleShot(0, update_overlay)
whereupdate_overlay
would only be called after the QML had been rerendered.This is not the case, as the following minimal working example shows. It has a button, which calls a C++ function, which emits a change that updates the QML (a rectangle becomes wider) AND has this
QTimer::singleShot(0, ...)
// main.cpp int main(int argc, char *argv[]) { qputenv("QSG_RENDER_LOOP", QByteArray("basic")); Controller controller; QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("Controller", &controller); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
// Controller.cpp -- it has the callback void Controller::click() { emit changeScene(); QTimer::singleShot(0, [](){ qDebug() << "Hello world"; }); }
// main.qml import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Window 2.12 Window { visible: true width: 640 height: 480 title: "After render calback" property int swaps: 0; onBeforeSynchronizing: console.log("Before synchronizing") onBeforeRendering: console.log("Before rendering") onAfterRendering: console.log("After rendering") onFrameSwapped: console.log("Frame swapped: " + (++swaps)) Rectangle { id: rect width: 20 height: 20 color: "blue" anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: Controller.click() } Connections { target: Controller onChangeScene: { console.log("Changing the scene") rect.width += 1 } } }
When clicking the button, what I'm getting is:
qml: Before synchronizing qml: Before rendering qml: After rendering qml: Frame swapped: 1 qml: Before synchronizing qml: Before rendering qml: After rendering qml: Frame swapped: 2 qml: Before synchronizing qml: Before rendering qml: After rendering qml: Frame swapped: 3 qml: Changing the scene Hello world qml: Before synchronizing qml: Before rendering qml: After rendering qml: Frame swapped: 4
What I was hoping for was that
QTimer::singleShot
would fire only after the next render / frame swap. So basically, what I wanted was:qml: Changing the scene qml: Before synchronizing qml: Before rendering qml: After rendering qml: Frame swapped: 4 Hello world
What would be a convenient way to execute that lambda in the callback only after QML has rerendered?
EDIT: in my application I am using the single threaded render loop with
qputenv("QSG_RENDER_LOOP", QByteArray("basic"));
- I have a custom
-
What about using callLater()? It runs after the current function has exited if I remember right.
-
@fcarney thanks for your reply :) I tried it like this: adding a slot to the controller
void Controller::click() { emit changeScene(); } void Controller::later() { qDebug() << "Hello world"; }
and handling the
onClicked
asonClicked: { Qt.callLater(Controller.later) Controller.click() }
but unfortunately
Qt.callLater
is handled before the scene is rerendered:... qml: Frame swapped: 3 qml: Changing the scene Hello world qml: Before synchronizing qml: Before rendering qml: After rendering qml: Frame swapped: 4
-
Okay, set a variable flag on your event. Then check that variable in the onFrameSwapped event. Then use callLater (if needed) or just do your thing at that point. Reset the variable flag.
-
Yes, exactly, something like this is what I wrote. I was just hoping there was a convenient way to keep the code for all this local to a single C++ function, but maybe that's too much to ask.