How to get a ListModel to update from a signal



  • So I have a ListView with delegates of Rectangles that contain Text. It uses a ListModel that which when completed, appends more ListElements that is read from a C++ class method which iterates a QStringList. Essentially it is a Logger that functions well at the start but only at the start of the application. I'm not sure if I want to use Component.onCompleted and need to use a signal from the C++ class to continue to update the ListModel. I've tried using onLogLinesChanged() as a signal but keep getting an error "Non-existent attached object". Here is a portion of my code so far.

    ListView {
    id: logList_Id
    boundsBehavior: Flickable.StopAtBounds
    model: listModel
    clip: true
    anchors.fill: parent
    delegate: Rectangle {

                    id: rectangle
                    width: logList_Id.width
                    height: textLogs_Id.height
                    color: index % 2 == 0 ? "#bdbdbd" : "#ffffff"
    
                    Text {
                        
                        id: textLogs_Id
                        text: logString
                        wrapMode: Text.WordWrap
                        width: parent.width
                    }
                }
    

    }

    ListModel {

    //    Connections {
    //                    target: LibXpcWorkerApi
    //                    onlogLinesChanged: {
    //                        listModel.append()
    //                    }
    //                }
    
          id: listModel
          Component.onCompleted: {
                    LibXpcWorkerApi.setLogLines();
                    for(var i=0; i<LibXpcWorkerApi.logLinesSize(); i++)
                        append(createListElement(i));
                }
    
                function createListElement(i) {
                    return { logString: LibXpcWorkerApi.logLinesAt(i)};
                }
    
    //          LibXpcWorkerApi.onLogLinesChanged: listModel.append(LibXpcWorkerApi.logLinesAt(0));
    

    }



  • You can expose the model from c++ itself. Please look at examples directory of qt installations. It is easy.



  • That is an incredibly vague answer and does not help me whatsoever. I already have the signals connected and can call class members from qml. I do not know nor can find any information on what is the correct qml implementation for updating a ListModel once it receives a signal.



  • Hi,

    I have created a sample application. Hope this will help you to get what u want.

    /// main.cpp

    #include <QApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "myviewmodel.h"

    int main(int argc, char *argv[])
    {
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;
    
    QQmlContext* context = engine.rootContext();
    MyViewModel myModel;
    context->setContextProperty("myViewModelInstance", &myModel);
    
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
    return app.exec();
    

    }

    /// myviewmodel.h
    #ifndef MYVIEWMODEL_H
    #define MYVIEWMODEL_H

    #include <QObject>

    class MyViewModel : public QObject
    {
    Q_OBJECT
    public:
    Q_PROPERTY(QStringList dataList READ dataList NOTIFY dataListChanged)
    explicit MyViewModel(QObject *parent = 0);
    Q_INVOKABLE void setLogLines();
    Q_INVOKABLE void updateModel(const QString& value);
    QStringList dataList();

    signals:
    void getLatest();
    void dataListChanged();

    public slots:
    void onGetLatest();

    private:
    QStringList m_myData;
    };

    #endif // MYVIEWMODEL_H

    /// myviewmodel.cpp

    #include <QVariant>
    #include "myviewmodel.h"

    MyViewModel::MyViewModel(QObject *parent) : QObject(parent)
    {
    connect( this, SIGNAL( getLatest()),
    this, SLOT( onGetLatest()),
    Qt::DirectConnection );
    }

    void MyViewModel::setLogLines()
    {
    QString item("Name: %1");
    for(int i = 0; i < 5; ++i)
    {
    m_myData.append(item.arg(i+1));
    }
    emit dataListChanged();
    }

    void MyViewModel::updateModel(const QString& value)
    {
    m_myData.append(value);
    emit dataListChanged();
    }

    void MyViewModel::onGetLatest()
    {
    /// Update the model and emit the signal. So that QML will update.
    updateModel("from C++");
    }

    QStringList MyViewModel::dataList()
    {
    return m_myData;
    }

    /// main.qml
    import QtQuick 2.5
    import QtQuick.Controls 1.4
    import QtQuick.Dialogs 1.2

    ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    ListView {
    id: logList_Id
    boundsBehavior: Flickable.StopAtBounds
    model: myViewModelInstance.dataList /// Propery exposed from C++
    clip: true
    anchors.fill: parent
    delegate: Rectangle {
    
                    id: rectangle
                    width: logList_Id.width
                    height: textLogs_Id.height
                    color: index % 2 == 0 ? "#bdbdbd" : "#ffffff"
    
                    Text {
    
                        id: textLogs_Id
                        text: modelData
                        wrapMode: Text.WordWrap
                        width: parent.width
                    }
                }
    }
    
    ListModel {
          id: listModel
          Component.onCompleted: {
                    //LibXpcWorkerApi.setLogLines();
                    myViewModelInstance.setLogLines();
                    for(var i=0; i<10/*LibXpcWorkerApi.logLinesSize()*/; i++)
                        append(createListElement(i));
                }
    
                function createListElement(i) {
                    //return { logString: LibXpcWorkerApi.logLinesAt(i)};
                    return ({"action": "preset"});
                }
    
    }
    Button {
        id: button1
        width: 100
        height: 50
        text: "ADD"
        anchors.bottom: parent.bottom
        onClicked: {
            myViewModelInstance.updateModel("from QML"); // Add an item in the list.
        }
    }
    Button {
        id: button2
        width: 100
        height: 50
        text: "ADD"
        anchors.bottom: parent.bottom
        anchors.left: button1.right
        onClicked: {
            myViewModelInstance.getLatest(); /// Trigger model c++ class to update the list.
        }
    }
    

    }



  • @flowolf I think "Non-existent attached object" comes because you have
    target: LibXpcWorkerApi
    LibXpcWorkerApi.setLogLines();
    LibXpcWorkerApi.logLinesAt(0)

    which begin with a capital letter. Either your LibXpcWorkerApi should be a type which actually has attached properties or it should be an object called libXpcWorkerApi.



  • @Eeli-K said in How to get a ListModel to update from a signal:

    @flowolf I think "Non-existent attached object" comes because you have
    target: LibXpcWorkerApi
    LibXpcWorkerApi.setLogLines();
    LibXpcWorkerApi.logLinesAt(0)

    which begin with a capital letter. Either your LibXpcWorkerApi should be a type which actually has attached properties or it should be an object called libXpcWorkerApi.

    This capital letter requirement has gotten me a few times. It's one of the things I wish would change about QML.



  • I actually figured it out on my own but thanks for taking the time to help me out.

    I did not use QQml Context, rather qmlRegisterSingletonType. Inside the LibXpcWorkerApi ctor I connect a signal to a slot which also passes a string. Inside slot, it takes that string and modifies a private member QString mLog and it ends with emitting a signal that the log has changed.
    Inside qml, for whatever reason, Connections can't be too far embedded in a qml file rather needs to be further out of scope towards the top.
    Eeli K, your right about the capital letter. So for anyone else attempting to append strings to a qml component based on signals, here's the short.

    LibXpcWorkerApi ctor { connect(mXpcLib, SIGNAL(signalLogLineAdded(const QString), this, SLOT(slotLogAdded(const QString))); }

    void LibXpcWorkerApi::slotLogAdded(const QString logline)
    {
    if(mLog != "") mLog= "";
    mLog = logline;
    emit logChanged();
    }

    qml file:
    Connections
    {
    target: LibXpcWorkerApi
    onLogChanged: listModel_Id.append(showLogsRect_Id.addLogElement());
    }

    function addLogElement()
    {
    return { logString: LibXpcWorkerApi.getLog()};
    }

    append will not take strings, only objects so you have to set text inside a delegate as logString or whatever matches in your return statement.
    I stored logs one string at a time for better memory management, for what I'm doing isn't required to stay in memory in a c++ class.


Log in to reply
 

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