Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Convenient way to execute C++ code after QML has rerendered

Convenient way to execute C++ code after QML has rerendered

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
5 Posts 2 Posters 1.7k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    stabbles
    wrote on last edited by stabbles
    #1

    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:

    1. I have a custom MyVTKModel {} QML element that uses QQuickFramebufferObject to render a 3D model.
    2. 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.
    3. This means Overlay{} should update only after MyVTKModel{} 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) where update_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"));

    1 Reply Last reply
    0
    • fcarneyF Offline
      fcarneyF Offline
      fcarney
      wrote on last edited by
      #2

      What about using callLater()? It runs after the current function has exited if I remember right.

      C++ is a perfectly valid school of magic.

      S 1 Reply Last reply
      0
      • fcarneyF fcarney

        What about using callLater()? It runs after the current function has exited if I remember right.

        S Offline
        S Offline
        stabbles
        wrote on last edited by
        #3

        @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 as

        onClicked: {
            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
        
        1 Reply Last reply
        0
        • fcarneyF Offline
          fcarneyF Offline
          fcarney
          wrote on last edited by
          #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.

          C++ is a perfectly valid school of magic.

          1 Reply Last reply
          0
          • S Offline
            S Offline
            stabbles
            wrote on last edited by
            #5

            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.

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved