Signal from c++ to QML with setContextProperty



  • Hello,
    I do an HTTP request and get a response from it .
    I check the http code status and emit a signal to a qml component to call a slot.
    My goal is to do a simple login form, with api call. When user log in, the qml login form switch to qml index component.
    For that, onSignal, I would like to change visible value.

    request.h

    class request : public QObject, public std::enable_shared_from_this<request>
    {
        Q_OBJECT
    
    signals:
        Q_INVOKABLE void login();
    

    request.cpp

    void request::on_read(boost::system::error_code ec, std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);
    
        if(ec)
            return fail(ec, "read");
    
        //TODO : Improve to get the user infos
        pt::ptree root;
        std::stringstream stringstream;
        stringstream << res.body();
        pt::read_json(stringstream, root);
    
        switch(res.result_int())
        {
        case 200:
            emit login(); //here the signal
            std::cout<<res.result_int()<<std::endl; //I see '200'
            break;
    

    main.cpp

    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
        QQmlApplicationEngine engine;
    
        boost::asio::io_context io_context;
        boost::asio::ssl::context ctx{ssl::context::sslv23_client};
        load_root_certificates(ctx);
        request request(io_context, ctx);
    
        engine.rootContext()->setContextProperty("http", &request);
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    }
    

    main.qml

    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Chat ")
        
        Connections {
            target: http
            onLogin: {
               console.log("login ok ! ") //Here I can't see signal
            }
        }
    
        Index {  
            id: chat
            visible: false
        }
    

  • Lifetime Qt Champion

    Hi and welcome to devnet,

    From the looks of it, I would guess that your network request has finished before your GUI is shown.

    Since you're not using QNetworkAccessManager, I would suggest to implement a slot that starts the request on demand.

    By the way, there's no need to add Q_INVOKABLE to your signals. You don't call them from external objects.



  • I do not use QNetworkAccessManager, but only Qt as a Gui app. The backend use another framework.

    I already start a slot into Qml like this :
    Signin.qml

        Button {
            id: send
            x: 280
            y: 257
            width: 61
            height: 25
            text: qsTr("Connect")
            onClicked:{
                http.start_request("myapi.com", "443", "/login", 11, "POST", "username=" + login.text + "password=" + password.text);
            }
        }
    

    I import this component (Signin.qml )into main.qml like this :

    
        SwipeView {
            id: swipeView
            anchors.fill: parent
            currentIndex: tabBar.currentIndex
            visible: true
            Signin {
                id: signin
            }
    }
    

    I already try to move connection into Signin.Qml but I have the same issue.

    Start_request slot do a shared_ptr :

    std::make_shared<request>(ioc, ctx)->run( );
    

    run function call function on_resolve, which call on_connect, which call on_handshake, which call on_write which call on_read, where is emitted the signal .



  • @Vana said in Signal from c++ to QML with setContextProperty:

    case 200:
    emit login(); //here the signal
    std::cout<<res.result_int()<<std::endl; //I see '200'

    Where you are calling this on_read(boost::system::error_code ec, std::size_t bytes_transferred) function. i think the control is not coming into this method. you just confirm whether the control is coming into this method by putting some logs.



  • I call on_read(boost::system::error_code ec, std::size_t bytes_transferred) only from c++ side.

    Here the request class.cpp

    #include "request.h"
    #include <iostream>
    #include <boost/beast/http/verb.hpp>
    #include <boost/utility/string_view.hpp>
    
    request::request(boost::asio::io_context& ioc, ssl::context& ctx) : resolver(ioc) , stream(ioc, ctx)
    {
    }
    
    void request::fail(boost::system::error_code ec, char const* what)
    {
        std::cerr << what << ": " << ec.message() << "\n";
    }
    
    void request::run(char const* host, char const* port, char const* target, unsigned int version, QString method, QString body)
    {
        // Set up an HTTP GET request message
        http::verb method_verb;
        boost::string_view method_strview ;
        method_strview = method.toStdString();
        method_verb = boost::beast::http::string_to_verb(method_strview);
        req.version(version);
        req.method(method_verb);
        req.target(target);
        req.set(http::field::host, host);
        req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
        req.set(http::field::content_type, "application/x-www-form-urlencoded");
        req.body() = body.toStdString() ;
        req.prepare_payload();
    
        auto self = shared_from_this();
        resolver.async_resolve(host, port, [self](boost::system::error_code ec, tcp::resolver::results_type result)
                                           {
                                                self->on_resolve(ec, result);
                                           });
     }
    
    void request::on_resolve(boost::system::error_code ec, tcp::resolver::results_type results)
    {
        if(ec)
        {
            //return fail(ec, "resolve");
        }
        // Make the connection on the IP address we get from a lookup
        std::shared_ptr self = shared_from_this();
        boost::asio::async_connect(stream.next_layer(), results.begin(), results.end(), [self](boost::system::error_code ec, tcp::resolver::iterator)
                                                                                        {
                                                                                            self->on_connect(ec);
                                                                                        });
    }
    
    void request::on_connect(boost::system::error_code ec)
    {
        if(ec){
            //return fail(ec, "connect");
            }
    
        // Perform the SSL handshake
        auto self = shared_from_this();
        stream.async_handshake(ssl::stream_base::client, [self](boost::system::error_code ec)
                                                         {
                                                            self->on_handshake(ec);
                                                         });
    }
    
    void request::on_handshake(boost::system::error_code ec)
    {
        if(ec){
            return fail(ec, "handshake");
        }
    
        // Send the HTTP request to the remote host
        auto self = shared_from_this();
        http::async_write(stream, req, [self](boost::system::error_code ec, std::size_t bytes_transferred)
                                       {
                                            self->on_write(ec, bytes_transferred);
                                       });
    }
    
    void request::on_write(boost::system::error_code ec, std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);
    
        if(ec)
            return fail(ec, "write");
    
        // Receive the HTTP response
        auto self = shared_from_this();
        http::async_read(stream, buffer, res, [self](boost::system::error_code ec, std::size_t bytes_transferred)
                                              {
                                                self->on_read(ec, bytes_transferred); // Here the on_read() call
                                              });
    }
    
    void request::on_read(boost::system::error_code ec, std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);
    
        if(ec)
            return fail(ec, "read");
    
        //TODO : Improve to get the user infos
        pt::ptree root;
        std::stringstream stringstream;
        stringstream << res.body();
        pt::read_json(stringstream, root);
    
        switch(res.result_int())
        {
        case 200:
            emit login(); //Here the signal
            std::cout<<res.result_int()<<std::endl; // I can see this
            break;
        case 401:{
            if (root.get<std::string>("message") ==  "wrong username"){
                std::cout<<"Unknow User"<<std::endl;
                emit wronglogin();
                }
            if (root.get<std::string>("message") ==  "wrong password"){
                std::cout<<"Wrong password"<<std::endl;
                emit wrongpassword();
                }
            }
            break;
        }
    
        // Gracefully close the stream
         auto self = shared_from_this();
         stream.async_shutdown([self](boost::system::error_code ec)
                               {
                                    self->on_shutdown(ec);
                               });
    }
    
    void request::on_shutdown(boost::system::error_code ec)
    {
        if(ec == boost::asio::error::eof)
        {
            ec.assign(0, ec.category());
        }
        if(ec)
          return fail(ec, "shutdown");
    }
    
    void request::start_request(QString host, QString port, QString target, unsigned int version, QString method, QString body)
    {
        boost::asio::io_context ioc;
        ssl::context ctx{ssl::context::sslv23_client};
    
        const char* std_host = host.toStdString().c_str();
        const char* std_port = port.toStdString().c_str();
        const char* std_target = target.toStdString().c_str();
        const char* std_method = method.toStdString().c_str();
        const char* std_body = body.toStdString().c_str();
    
        std::make_shared<request>(ioc, ctx)->run(std_host, std_port, std_target, version, std_method, std_body );
    
        ioc.run();
    }
    

    So from QML side, I do this into main.QML:

     Connections {
            target: http
            onLogin: {
               console.log("login ok")
            }
        }
    

    into Signin.qml i start the http request like this:

     Button {
            id: send
            x: 280
            y: 257
            width: 61
            height: 25
            text: qsTr("Connect")
            onClicked:{
                http.start_request("myapi", "443", "/login", 11, "POST", "username=" + login.text + "&password=" + password.text);
            }
        }
    

    So I can not call on_read() from QML, I only need connect login signal from on_read to QML


  • Qt Champions 2017

    The way you have placed the code it should work. You are telling that it is not working. Issue can happen only if

    1. Signal is not emitted
    2. Connection is made after sending the signal
    3. Sender object & receiver objects are different
    4. Objects are destroyed before anything meaning full take place.

    Just ensure that your login() signal is indeed working, try to place the following in your main.cpp & see the login() signal is really working.

    request myrequest(io_context, ctx);
    QObject::connect(&myrequest,&request::login,[](){
        qDebug() << "Login Successfull" <<endl;
    });


  • I add this into main.cpp, and I see nothing, only "200" from std::cout<<res.result_int()<<std::endl;

    So the signal is not emitted ?


  • Qt Champions 2017

    It is as expected as signal is sent by some other object, we are connecting to different object. Now change your main.cpp to register object like the following. Then see what happens. Please change http->http1. This requires change in qml also from http->http1.

    request myrequest(io_context, ctx);
    engine.rootContext()->setContextProperty("http1", &myrequest);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    QObject::connect(&myrequest,&request::login,[](){
        qDebug() << "Login Successfull" <<endl;
    });
    


  • I change request to http1, still have issue.

    Here all source for the concerned class:


  • Qt Champions 2017

    @dheerendra said in Signal from c++ to QML with setContextProperty:

    qDebug() << "Login Successfull" <<endl;

    Did the above debug message come ?


  • Qt Champions 2017

    Following line is culprit for your issue.

    std::make_shared<request>(ioc, ctx)->run(std_host, std_port, std_target, version, std_method, std_body );


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.