Show different qml ui from c++



  • Hi,

    I'm trying to make a application with qml UI and C++ backend.
    When I run the application login screen comes up which is main.qml in my project.

    I have another application.qml file which is the UI that should show up when login is successful.
    What is the best way to achieve this? I'm not sure how to proceed here.

    Thanks
    firefox


  • Qt Champions 2017

    Hi
    What about emit signal from the login screen to the c++ program if login is valid?
    Then c++ can show next QML file.
    http://wisol.ch/w/articles/2014-12-15-qt-signal-slots-qml-cpp/



  • HI mrji,

    I was trying to do that. My C++ class emits a signal if login is successful which is connected to a C++ slot which tries engine.load(path to new qml file). But it doesn't work. Is it correct way to show new qml UI?


  • Qt Champions 2017

    @firefox
    Are u sure the slot is called ?

    Can engine.load return some status ?



  • This is my slot that gets called when login is succesful.

    void Login::showClient()
    {
        cout<<"trying to load client"<<endl;
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/BuddyList.qml")));
    
    }
    

    my cout statement gets printed that means slot is indeed getting called.
    Should I really be creating a new engine and load the qml file is there an elegant way of doing it?


  • Moderators

    engine is a local variable and gets destroyed when showClient() finishes.


  • Qt Champions 2017

    @firefox said:
    well this seems wrong as
    QQmlApplicationEngine engine;
    is a local variable and will be deleted as soon as function ends.

    Cant you use the original engine that loads the login?



  • Thanks,

    that was a brain fart on my side.
    This is how my slot looks like now

    void Login::showClient()
    {
        emit loginSuccessful();
        cout<<"trying to load client"<<endl;
        Q_ASSERT(engine);
        engine->load(QUrl(QStringLiteral("qrc:/BuddyList.qml")));
    }
    

    Now the problem is login.qml is still shown even after loading new qml file to same engine.
    To overcome this, I added a new signal that is emitted when login is succesful that is picked by login.qml and set's it visible property to false.

    But this seems like a hack and I'm guessing the login.qml instance still lingers around in memory. Do you guys suggest any better way to unload the login.qml from engine?


  • Qt Champions 2017

    Hi

    • that was a brain fart on my side.
      Its a classic fart :)

    Im have not used QML so much to know if there is better way
    and not sure how to unload using engine.

    We can try to ask @Wieland ;)



  • @mrjj

    Thanks mrjj.
    I'm new to this forum. How to include @Wieland to this discussion? Will he be getting a notification if I @mention him.


  • Qt Champions 2017

    @firefox
    yes he will :)
    Hopefully he dont mind :)



  • Hi, I will look into this tomorrow, I promise!



  • Ok, this is what I would do:

    1. backend in C++, exposed to QtQuick
    2. main.qml with the main application window
    3. Login.qml with the login window
    4. In main.cpp: use QQmlApplicationEngine and engine.load() to show main.qml as always
    5. main.qml has its visible property set to false on startup
    6. Login.qml is a Window from QtQuick.Window 2.0
    7. main.qml has such a login window as a child
    8. on startup the login window's visible property is set to true

    -> on startup both the main window and the login window are loaded but only the login window is visible

    1. the backend has one slot and two signals
    2. the login window calls the backend's slot after the user has entered her password and clicked on a login button
    3. the backend's slot takes the entered password as argument and checks if it's correct
    4. if the password is correct the backend emits a "loginOk" signal, otherwise it emits a "loginFailed" signal
    5. the login window has a slot connected to the backend's loginFailed signal. if it receives this signal it will tell the user to try it again

    -> if password is wrong login window says "try again"

    1. the main window has a slot connected to the backends loginOk signal
    2. if it receives this signal it will make the login window invisible and make itself (the main window) visible

    If you like the idea then I can post a working example.
    Cheers!



  • Oh, I forgot this: If you're really concerned about the memory the login window consumes then you can also create and destroy the login window dynamically from within main.qml.



  • @Wieland
    Thank you. I like your idea. I'm new to qml but I'd like to try it out myself first and see if I can get it working.
    I'd like to know about creating and destroying the login window dynamically. I think I would need it as the application is a chat client and I'd require to create and destroy chat windows.



  • @firefox The official docs have the necessary info for dynamic creation / destruction, see: Dynamic QML Object Creation from JavaScript.


  • Qt Champions 2017

    @Wieland
    Isn't this a fairly complex solution for quite the simple problem?
    (I'm not arguing about the solution itself, only pondering about QML)



  • @Wieland

    I created a login.qml and added it as child of main.qml and visible set to true
    main.qml has it's visible property set to false and when it receives loginSuccesful signal it's visible is set to true and login's visible is set to false.

    ApplicationWindow {
        id: mainClientId
        visible: false
        height: 640
        width:480
        minimumHeight: 500
        minimumWidth: 400
        color: "#cecece"
    
    
        Login {   //This is defined as window in Login.qml
            visible: true
            id: loginClientId
        }
    
    
        Connections {
            target: loginClass
            onLoginSuccessful: {
                console.log("in Connections")
                mainClientId.visible = true
                loginClientId.visible = false
            }
    
        }
    }
    

    But since the parent's visible property is false, even login window doesn't show up when i run the application. Am I doing it right?

    @kshegunov what do you suggest here?


  • Qt Champions 2017

    @firefox
    I don't work with QML, so I'm not suggesting anything. I was simply surprised it looks so complicated in QML (for the widgets module, which I use, what you're asking would be a two line snippet).



  • Okay, Just realized the mistake.

    This is how main.qml looks now and it works.

    
    Item {
    
        Client {
            id: clientId
            visible:false
        }
    
        Login {
            id: loginId
            visible: true
        }
    
    
        Connections {
            target: loginClass
            onLoginSuccessful: {
                console.log("in Connections")
                clientId.visible = true
                loginId.visible = false
            }
    
        }
    }
    
    


  • folks,

    got another problem

    I have this signal messageReceivedToQml emitted by my c++ backend when a new message arrives.
    And I connected to my qml like this

    Connections {
            target: loginClass
            onMessageReceivedToQml: {
                console.log("in client connections")
                Script.createChatWindow()
            }
        }
    

    my createChatWindow javascript function is

    function createChatWindow() {
        console.log("creating chat window");
        var chatWindow2 = Qt.createComponent("ChatWindow.qml");
        chatWindow2.createObject(clientId);
    }
    
    

    the log 'creating chat window' gets printed when i receive a message but the chat window created is not visible. Am I missing something here?


  • Qt Champions 2017

    Hi
    I dont know QML but
    i was wondering if it needs something like
    chatWindow2.visible = true
    or
    chatWindow2.show()

    Sorry if its just silly. :)



  • @mrjj Even I'm new to qml. nothing is sily to me :)

    I can't beleive i did the same mistake of using local variables instead of global. (Update: this is not the issue. Just had to delete the build folder and build again)
    I changed the code as below.

    var chatWindow;
    var sprite;
    function createChatWindow() {
        console.log("creating chat window");
        chatWindow = Qt.createComponent("ChatWindow.qml");
        sprite=chatWindow.createObject(clientId);
    }
    
    

    Update:Okay, It worked after the above changes. I just had to delete the build folder and build again.

    damn i edited the post instead of new post and everything is gone. edited again and added the code.



  • Hi folks,

    Have an issue here. have a look at this code I'm trying.

    
    function handleMessage(from, msg) {
        var mysprite = loginClass.checkJid(from);  //This method returns the QQuickWindow object
        if(mysprite != "not found")
        {
            console.log("in append block")   //This means this is a connected client and is not the first message. So we just append the message to chatAreaId(TextArea)
            mysprite.chatAreaId.append(from+":"+msg);   //chatAreaId is the id of TextArea component 
        }
        else {
            mysprite = createChatWindow();
            console.log("in chat window block")    //This means the client is connecting to us for first time. so we need to create a dedicated chat window for the client  and add the id of client to backend with addToJids
            loginClass.addToJids(from, mysprite);
            mysprite.chatAreaId.append(from+":"+msg);
        }
    }
    
    function createChatWindow() {
        var chatWindow;
        var sprite;
        console.log("creating chat window");
        chatWindow = Qt.createComponent("ChatWindow.qml");
        sprite=chatWindow.createObject(clientId);   //This is a QQuickWindow
        return sprite;
    }
    

    So when I get a message I want to append it to TextArea component of my QQuickWindow object created dynamically with createObject.() function. But how do I access the qml components of QQuickWindow created dynamically?

    The above code gives an error saying "Cannot call method 'append' of undefined"



  • Is this even a proper approach. I have a QHash in my c++ backend of type <QString, QQuickWindow>
    which stores key value pairs of clientId and chat window instance for the client.

    So when my application gets a incoming message, It first checks if there is already chat window instance for the client and gets the instance and appends the message to that window.

    If the client is connecting for first time, a new chat window is created and is added to QHash



  • Please show us your checkJid() function.



  • @firefox said:

    Is this even a proper approach.

    Well, you're building a backend in C++ and the GUI in QML. Now you start managing windows (parts of the GUI) in C++ again. Of course it will work but IMAO it's bad design. You should really do all GUI related stuff in QML.



  • Yeah. I don't want to deal with QQuickWindow objects in C++. The control already seems to sphagettified and confused to whoever is reading the code. I'm thinking of a different approach.

    Here is the requirement.

    When we get a message, C++ emits a signal messageReceived(from, message). It should be qml's responsibility to direct the message to appropriate chat window/create a new chat window if required.

    The only thoughts I'm getting are creating map/hash like some data structure in qml that can hold a key and reference to chat window objects. I'm researching if it is possible in qml. Guess I'm still thinking in C++ way instead of qml way. You guys suggest anything ?



  • @firefox said:

    When we get a message, C++ emits a signal messageReceived(from, message).

    I wouldn't even do that. Better store all messages in a model in the backend, use a custom view to display / modify the data in QtQuick and let the model / view do all the synchronization.



  • Hi guys,

    An update.
    So when I get the user gets a message, back-end parses the message and emits a messagaReceived(from,msg) signal.

    Qml picks up the messageReceived signal and is connected to handleMessage javascript slot to direct the message to appropriate window.

    var windows = [];
    function handleMessage(from, msg) {
    
        for(var i=0; i<windows.length;i++) {
            if(windows[i].jid == from) {
                console.log("FOUND MATCHING WINDOW");
                windows[i].chatBox.append(msg);
                return;
            }
        }
        var mysprite = createChatWindow(from);
        console.log("in chat window block")    //This means the client is connecting to us for first time. so we need to create a dedicated chat window for the client  and add the is of client to backend with addToJids
        mysprite.chatBox.append(msg);
    }
    
    function createChatWindow(from) {
        var chatWindow;
        var sprite;
        console.log("creating chat window");
        chatWindow = Qt.createComponent("ChatWindow.qml");
        sprite=chatWindow.createObject(clientId,{"jid":from});  //This is a QQuickWindow
        windows.push(sprite);
        console.log("test "+sprite.jid);
        return sprite;
    }
    
    

    I think I got the core functionality working. I'll be prettying up the UI little bit before adding other features.

    @Wieland said:

    @firefox said:

    When we get a message, C++ emits a signal messageReceived(from, message).

    I wouldn't even do that. Better store all messages in a model in the backend, use a custom view to display / modify the data in QtQuick and let the model / view do all the synchronization.

    I tried this but couldn't get a working prototype because of my inexperience with models, views(guess qml in general). I'll try to refactor the above working method to something elegant soon. Thanks. :)


Log in to reply
 

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