Is there a way to use QEventLoop as the main event loop in a QML application?

788 Views Asked by At

QML applications do not exit when using QEventLoop. The window will open just fine, but when the application window is closed, the program does not exit. It also doesn't fire any events like QGuiApplication::lastWindowClosed or QQmlApplicationEngine::destroyed. Try running the example below to see what I mean. You have to hit CTRL-C for it to exit.

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QEventLoop>
#include <string>

const std::string qmlStr = R"(
import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    title: "My Application"
    width: 640
    height: 480
    visible: true

    Button {
        text: "Push Me"
        anchors.centerIn: parent
    }
})";

int main(int argc, char** argv) {
    auto app = new QGuiApplication(argc, argv);
    auto m_engine = new QQmlApplicationEngine();

    m_engine->loadData(QByteArray::fromStdString(qmlStr));

    QObject::connect(QGuiApplication::instance(), &QGuiApplication::aboutToQuit, []() {
        qDebug("aboutToQuit");
    });

    QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, []() {
        qDebug("lastWindowClosed");
    });

    QObject::connect(m_engine, &QQmlApplicationEngine::destroyed, []() {
        qDebug("Destroyed");
    });

    QEventLoop loop;
    while (loop.isRunning()) {
        loop.processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents | QEventLoop::EventLoopExec);
    }
    // loop.exec() doesn't work either.

    return 0;
}

Is there any way to use QEventLoop as the main event loop in a QML application?

NOTE: Yes, I do need to use QEventLoop as the main event loop. qGuiApp->exec() works, but it's not what I need.

1

There are 1 best solutions below

0
Ville Voutilainen On

I got this working with a couple of changes.

  1. Add a signal into your ApplicationWindow, and emit it from an onClosing handler:
    ApplicationWindow {
        title: "My Application"
        width: 640
        height: 480
        visible: true
    
        signal get_out
    
        Button {
            text: "Push Me"
            anchors.centerIn: parent
        }
        onClosing: {
            get_out()
        }
    }
  1. The modified code is so that we hook that signal into a helper type that just sets a flag, and we quit our loop if that flag is set:
    m_engine->loadData(QByteArray::fromStdString(qmlStr));
    QEventLoop loop;
    
    bool ffs_quit_that_loop = false;
    BarkingMad bm(ffs_quit_that_loop);
    
    QObject* rootObj = m_engine->rootObjects().at(0);
    QObject::connect(rootObj, SIGNAL(get_out()), &bm, SLOT(run()));
    
    while (!ffs_quit_that_loop)  {
        loop.processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents);
    }
  1. The helper type is simple, but since it's a QObject, we need to define it in a separate header/source pair so that the usual moc build just works, so the header:
    #ifndef BARKINGMAD_H
    #define BARKINGMAD_H
    
    #include <QObject>
    
    class BarkingMad : public QObject
    {
        Q_OBJECT
    public:
        explicit BarkingMad(bool& endFlag, QObject *parent = nullptr);
    
    public slots:
        void run();
    private:
        bool& m_endFlag;
    };
    
    #endif // BARKINGMAD_H

and the source:

    #include "barkingmad.h"
    
    BarkingMad::BarkingMad(bool& endFlag, QObject *parent)
        : QObject{parent}, m_endFlag(endFlag)
    {
    
    }
    
    void BarkingMad::run()
    {
        m_endFlag = true;
    }