0

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.

Alex Shaw
  • 163
  • 1
  • 7
  • I think I've already seen a similar question some time ago. Btw what is your _real_ question? why don't you just want to use `QApplication::exec()`? What does your "_not what I need_" mean? – folibis Dec 20 '21 at 11:06
  • @folibis Because I am using this for bindings to another programming language. `QApplication::exec()` blocks the event loop, so signals will never work. If the above example worked, I would have `loop.isRunning` and `loop.processEvents` be running in my scripting language, not in C++. Does that help? – Alex Shaw Dec 20 '21 at 12:21
  • Looks really strange for me mainly running qml application event loop from external application. Probably this is an incorrect application architecture. At least because of the lack of details, I think. How does this binding should work? If you really want to implement that by yourself I guess the right way is to copy and modify the QApplication::exec() routine source code. – folibis Dec 20 '21 at 12:27
  • 1
    All of those signals and related behavior are specific to ::exec(). In this case, I'd hook the `closing` signal on ApplicationWindow in QML and call out to C++ to `loop.exit(0)`. For background, check out the implementation code here: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qguiapplication.cpp#L3519 – David K. Hess Dec 20 '21 at 12:42

1 Answers1

0

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;
    }