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

Emit signal from C++ to QML without any parameters



  • Hi.

    Im trying to implement logging mechanism into my app.

    What I've done so far is created LoginView.qml file:

    // LoginView.qml
    
    Item {
    
        id: loginView
        signal loginClicked(string textField_usr, string textField_pwd);
    
        objectName: "loginView"
    // ATTEMPTS SO FAR
    //   Connections {
    //       target: MasterController.ui_loginController
    //       onLoginSuccessful: MasterView.contentFrame.replace("qrc:/views/DashboardView.qml")
    //        onLoginFailed: console.log("login failed")
    //    }
    
    //            LoginController {
    //                id: loginController
    //                onLoginSuccessful: MasterView.contentFrame.replace("qrc:/views/DashboardView.qml")
    //                onLoginFailed: console.log("login failed")
    //            }
    
        GroupBox {
           /* Some styling, including  TextFields etc*/
    
            Button {
                id: button
                objectName: "proceed_button"
            
                /*more styling*/
    
                onClicked: {
                    loginClicked(username_textField.text, password_textInput.text)
                }
            }
        }
    }
    
    

    LoginView.qml gets loaded by MasterView.qml onCompleted()

    Window {
        /*Some stuff - nothing related to login process*/
    
       StackView{
            id: contentFrame
            /*...*/
    
            initialItem: Qt.resolvedUrl("qrc:/views/SplashView.qml")
        }
    
    
        Component.onCompleted: {
           contentFrame.replace("qrc:/views/LoginView.qml")
    
        }
    }
    
    

    With that in place, I have created LoginViewController class:

    namespace cms{
    namespace controllers{
    
    class CMSLIBSHARED_EXPORT LoginViewController : public QObject{
        Q_OBJECT
    
    private:
    
        QObject* loginButton {nullptr};
    
    public:
    
    
        explicit LoginViewController(QObject* _parent = nullptr) : QObject(_parent) {}
    
        LoginViewController(QQmlApplicationEngine* engine){
            QObject* loginView = engine->rootObjects().first()->findChild<QObject*>("loginView");
        
            loginButton = loginView->findChild<QObject*>("proceed_button");
    
            QObject::connect(loginView, SIGNAL(loginClicked(QString, QString)), this, SLOT(loginButtonClicked(QString, QString)));
    
        }
    
    
    public slots:
        void loginButtonClicked(QString user, QString pwd);
    
    signals:
        void loginSuccessful();
        void loginFailed();
    };
    
    }
    }
    

    LoginViewController is a member of MasterController, which I export as MasterController's Q_PROPERTY:

    Q_OBJECT
    Q_PROPERTY(cms::controllers::LoginViewController* ui_loginController READ loginController CONSTANT)
    
    public:
    LoginViewController* loginController();
    

    Here's a part that im not really sure of.
    I registered my LoginViewController class as QML type inside main.cpp

    qmlRegisterType<cms::controllers::MasterController>("CMS", 1, 0, "MasterController");
    
    //newly added
    qmlRegisterType<cms::controllers::LoginViewController>("CMS", 1, 0, "LoginController");
    

    Everything works fine when I emit the signal from Qml into C++, login function works as intended, but I am getting stucked when i try to emit different signals depending on login method output.

    After signal from Qml button gets emitted, it triggers login function

    void LoginViewController::loginButtonClicked(QString user, QString pwd)
    {
            std::cerr << "loginButtonClicked " << std::endl;
            if(cms::administration::Clinic::login(user.toStdString(), pwd.toStdString())){
                loginSuccessful(); return;
            }
            loginFailed();
    }
    

    As you can see, here I'd like to emit signal into QML. I came by different posts, different documentation pages, and i feel like left with too many tools to work with.

    Could somebody guide me, on how should I emit and handle the signal inside QML?
    Moreover, if there's any "better" or more suitable solution for this mechanism, I'd be happy to hear about it.

    Best regards!



  • I would do the following :

    • instead of registering your LoginViewController, I would simply create a context property for your controller instance. Something like :
    cms::controllers::LoginViewController controler;
    QQmlApplicationEngine engine;
    engine->rootContext()->setContextProperty("loginCtrl", this);
    
    
    • instead of retrieving the login button from c++ and create the connection etc...simply call loginButtonClicked on the controller property in the button onClicked handler.
    Button {
                id: button
                objectName: "proceed_button"
            
                /*more styling*/
    
                onClicked: loginCtrl.loginButtonClicked(username_textField.text, password_textInput.text)
            }
    
    • use a "Connection" item in QML with the controller property as target to react to loginSuccess/loginFailure
    Connection{
       target: loginCtrl
       onLoginSuccessful: {}
       onLoginFailed:{}
    }
    


  • I would do the following :

    • instead of registering your LoginViewController, I would simply create a context property for your controller instance. Something like :
    cms::controllers::LoginViewController controler;
    QQmlApplicationEngine engine;
    engine->rootContext()->setContextProperty("loginCtrl", this);
    
    
    • instead of retrieving the login button from c++ and create the connection etc...simply call loginButtonClicked on the controller property in the button onClicked handler.
    Button {
                id: button
                objectName: "proceed_button"
            
                /*more styling*/
    
                onClicked: loginCtrl.loginButtonClicked(username_textField.text, password_textInput.text)
            }
    
    • use a "Connection" item in QML with the controller property as target to react to loginSuccess/loginFailure
    Connection{
       target: loginCtrl
       onLoginSuccessful: {}
       onLoginFailed:{}
    }
    


  • Hi @Charby, thanks for your feedback.
    Your idea works just fine, but im getting those errors, which I cannot get rid of.

    I have added Connection item you have mentioned about:

    • inside LoginView.qml
    Connections {
            target: loginController
            onLoginSuccessful : { masterView.contentFrame.replace("qrc:/views/DashboardView.qml") }
            onLoginFailed: {console.log("Failed") }
        }
    
    • inside main.cpp
        cms::controllers::MasterController masterController;
    
        QQmlApplicationEngine engine;
        engine.addImportPath("qrc:/");
    
        engine.rootContext()->setContextProperty("masterController", &masterController);
    
        engine.load(QUrl(QStringLiteral("qrc:/views/MasterView.qml")));
    
        cms::controllers::LoginViewController lvc(&engine);
        engine.rootContext()->setContextProperty("loginController", &lvc);
    

    But it produces following errors:

    qrc:/views/LoginView.qml:13:5: QML Connections: Cannot assign to non-existent property "onLoginFailed"
    qrc:/views/LoginView.qml:13:5: QML Connections: Cannot assign to non-existent property "onLoginSuccessful"
    qrc:/views/LoginView.qml:14: ReferenceError: loginController is not defined
    

    Any ideas?



  • lvc needs to be defined as a context property before the qml engine call of load