Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Calling C++ slot from Qml signal



  • Hello everyone,

    For some days I've been trying to connect a QML signal to a C++ slot.
    I've followed this documentation:
    https://doc.qt.io/qt-6/qtqml-cppintegration-interactqmlfromcpp.html#connecting-to-qml-signals

    But there is some problems:
    If I use:

    QQuickView view(QUrl::fromLocalFile("SideMenuOpenCloseBtn.qml"));
    

    This message will appear in the console message at the start of the application: file:[...all the path to the qml file ]/SideMenuOpenCloseBtn.qml: No such file or directory.
    So i've changed it to:

    QQuickView view(QUrl(QStringLiteral("qrc:/SideMenuOpenCloseBtn.qml")));
    

    In this case the connection is valid but the cpp slot seems to not been called.

    Here is my code:

    • The button who send the signal on click
    Button {
        id: openCloseBtnId
        [Styling etc...]
    
        display: AbstractButton.IconOnly
    
        signal resizeMainView(isSideMenuOpen: bool)
    
        onClicked: {
            openCloseBtnId.resizeMainView(sideMenu.visible);
    
            if(sideMenu.visible) {
                sideMenu.close();
            }
            else {
                sideMenu.open();
            }
        }
    }
    
    • The c++ object who has to receive the signal
    #ifndef VIEWMANAGER_H
    #define VIEWMANAGER_H
    
    #include <QObject>
    #include <QDebug>
    
    class ViewManager : public QObject
    {
        Q_OBJECT
    public:
        explicit ViewManager(QObject *parent = nullptr);
    
    signals:
    
    public slots:
        void resizeMainView(bool isSideMenuOpen);
    };
    
    #endif // VIEWMANAGER_H
    
    #include "viewmanager.h"
    
    ViewManager::ViewManager(QObject *parent) : QObject(parent)
    {
    
    }
    
    void ViewManager::resizeMainView(bool isSideMenuOpen)
    {
        qInfo() << "Resizing window - is SideMenu open:" << isSideMenuOpen;
    }
    
    • main.cpp:
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QDebug>
    #include <QQuickItem>
    #include <QQuickView>
    #include "equationcomputer.h"
    #include "viewmanager.h"
    
    int main(int argc, char *argv[])
    {
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    #endif
    
        QGuiApplication app(argc, argv);
    
        EquationComputer equation;
        ViewManager viewManager;
    
        QQuickView view(QUrl(QStringLiteral("qrc:/SideMenuOpenCloseBtn.qml")));
        QObject *sideMenuOpenCloseBtn = view.rootObject();
    
        QObject::connect(sideMenuOpenCloseBtn, SIGNAL(resizeMainView(bool)), &viewManager, SLOT(resizeMainView(bool)));
        qDebug() << "Is connection valid:" << QObject::connect(sideMenuOpenCloseBtn, SIGNAL(resizeMainView(bool)), &viewManager, SLOT(resizeMainView(bool)));
    
        [Code auto-generated by QT]
    
        return app.exec();
    }
    

    Did I missed something ?

    Thank you for reading.
    Have a nice day.


  • Moderators

    You are calling connect() twice, your connection will be duplicated.

    Perhaps try SIGNAL(resizeMainView(QVariant)).

    But in general, I'd say it's quite wrong for documentation to suggest this approach. I've never seen this approach used in real life, and it is very unreliable to relay on rootObject() like this. Here is what I'd suggest you do:

    C++

    ViewManager viewManager;
    QQuickView view(QUrl(QStringLiteral("qrc:/SideMenuOpenCloseBtn.qml")));
    view.rootContext()->setContextProperty("viewManager", &viewManager);
    
    view.show();
    return app.exec();
    

    QML

    Button {
        id: openCloseBtnId
        [Styling etc...]
    
        display: AbstractButton.IconOnly
    
        signal resizeMainView(isSideMenuOpen: bool)
    
        onResizeMainView: viewManager.resizeMainView(isSideMenuOpen)
    
        onClicked: {
            openCloseBtnId.resizeMainView(sideMenu.visible);
    
            if(sideMenu.visible) {
                sideMenu.close();
            }
            else {
                sideMenu.open();
            }
        }
    }
    

    You can also use Connections component to do the same https://doc.qt.io/qt-5/qml-qtqml-connections.html


  • Moderators

    You are calling connect() twice, your connection will be duplicated.

    Perhaps try SIGNAL(resizeMainView(QVariant)).

    But in general, I'd say it's quite wrong for documentation to suggest this approach. I've never seen this approach used in real life, and it is very unreliable to relay on rootObject() like this. Here is what I'd suggest you do:

    C++

    ViewManager viewManager;
    QQuickView view(QUrl(QStringLiteral("qrc:/SideMenuOpenCloseBtn.qml")));
    view.rootContext()->setContextProperty("viewManager", &viewManager);
    
    view.show();
    return app.exec();
    

    QML

    Button {
        id: openCloseBtnId
        [Styling etc...]
    
        display: AbstractButton.IconOnly
    
        signal resizeMainView(isSideMenuOpen: bool)
    
        onResizeMainView: viewManager.resizeMainView(isSideMenuOpen)
    
        onClicked: {
            openCloseBtnId.resizeMainView(sideMenu.visible);
    
            if(sideMenu.visible) {
                sideMenu.close();
            }
            else {
                sideMenu.open();
            }
        }
    }
    

    You can also use Connections component to do the same https://doc.qt.io/qt-5/qml-qtqml-connections.html



  • Hello @sierdzio,
    Thank you for replying and sorry for the delayed response.
    I've tried your code but when I click on the button I have this error message:

    qrc:/SideMenuOpenCloseBtn.qml:19: ReferenceError: viewManager is not defined

    So after some research I've tried this:

    QQmlContext *ctx = engine.rootContext();
    ctx->setContextProperty("viewManager", &viewManager);
    

    It is working but I think it would be better to set this view manager only in the context property of the button where it is needed. And I don't understand why your solution is not working in my application.

    Edit: And I forgot to mention in my first message that when I click on this button (no matter if there is a signal or not) I have this error message:

    file:///C:/Qt/6.0.3/mingw81_64/qml/QtQuick/Controls/Windows/Button.qml:77:49: Unable to assign [undefined] to int


  • Moderators

    @Aymeric_Qt said in Calling C++ slot from Qml signal:

    QQmlContext *ctx = engine.rootContext();

    So you are using QQmlEngine? It was not in your original snippet so I didn't use it either.

    It is working but I think it would be better to set this view manager only in the context property of the button where it is needed.

    In such case, you can instantiate the C++ class in QML if you register it first with qmlRegisterType methods. It's a more advanced technique so I didn't mention it before. There are also a few other ways to achieve this. But in general don't worry about root context properties - they work fast and are easy to set up. And Qt Creator's code model understands them well, too.



  • Hey @sierdzio,
    @sierdzio said in Calling C++ slot from Qml signal:

    So you are using QQmlEngine? It was not in your original snippet so I didn't use it either.

    Because I didn't have any idea that we can have a QML project without using the QML engine (I've just created the QML project with QtCreator).

    In such case, you can instantiate the C++ class in QML if you register it first with qmlRegisterType > methods. It's a more advanced technique so I didn't mention it before. There are also a few other > ways to achieve this. But in general don't worry about root context properties - they work fast and > are easy to set up. And Qt Creator's code model understands them well, too.

    Ok I'll go with the root context property for now and I'll keep in my mind the qmlRegisterType.

    Thank you very much for your answers!


Log in to reply