Solved 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.2ApplicationWindow {
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.