Access List of Objects in QML
-
Hello,
I build a class for multiple clients in my program to store the connection data of the multiple clients responding, to show in QML (mostly QString, bool, int). Each Client should be stored in its own Object.
When I receive a response from a Client I create a new client-Object with the Data provided and store it in a QMap<QString, *client> with the IP (QString) as Key. This works quite fine and from C++ an I am able to access the Data.
Now I am stuck at the point of trying to access the collected data in the QMap in QML.
In the client-Class all Properties are setup with QProperty:
#ifndef MOVIECLIENT_H #define MOVIECLIENT_H #include <QObject> #include <QMetaType> #include <QMap> #include <QDebug> class MovieClient : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged) Q_PROPERTY(QString version READ version WRITE setVersion NOTIFY versionChanged) Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged) public: explicit MovieClient(QObject *parent = nullptr); MovieClient(const QString &name, const QString &version, const QString &address, const int &port, QObject *parent = nullptr); MovieClient(const MovieClient &other); ~MovieClient(); QString name() const; void setName(const QString &name); QString address() const; void setAddress(const QString &address); QString version() const; void setVersion(const QString &version); int port() const; void setPort(const int &port); signals: void nameChanged(); void addressChanged(); void portChanged(); private: // Connection-Information QString m_name; QString m_address; QString m_version; int m_port; public slots: }; Q_DECLARE_METATYPE(MovieClient) #endif // MOVIECLIENT_H
In the Receiver Class the QMap containing the MovieClients is located and on Message received a new Client ist created an added to the QMap. There I also used Q_PROPERTY to make the QMap know:
#ifndef UDPRECEIVER_H #define UDPRECEIVER_H #include <QObject> #include <QHostAddress> #include <QUdpSocket> #include <QtNetwork> #include <QVariant> #include <QVariantMap> #include <QJsonDocument> #include "movieclient.h" #include <QMap> typedef QMap<QString, MovieClient*> movies; class UdpReceiver : public QObject { Q_OBJECT Q_PROPERTY(movies movieClientList MEMBER movieClients) public: explicit UdpReceiver(QObject *parent = nullptr); UdpReceiver(const UdpReceiver &receiver); ~UdpReceiver(); Q_INVOKABLE void clearClientlist(); signals: void dataReceived(); void jsonClientListUpdated(QString jCList); void movieClientListCleared(); private slots: void processPendingDatagrams(); private: void createJSON(); // QMap der Clients QMap<QString, MovieClient*> movieClients; QUdpSocket udpSocket4; QHostAddress groupAddress4; }; Q_DECLARE_METATYPE(UdpReceiver) #endif // UDPRECEIVER_H
in the Main.cpp i set the Context Property for the UdpReceiverClass to Access it from QML
UdpReceiver uReceiver; qRegisterMetaType<MovieClient>("movieClient"); qRegisterMetaType<movies>("movies"); engine.rootContext()->setContextProperty("UdpReceiver", &uReceiver);
and when I try to access now in QML the autocomplete goes up to:
console.log(UdpReceiver.movieClientList)
and there it stops, none of the geter/setter Methods are accessible.
The console.log when i send this out states "QVariant(QMap<QString,MovieClients*>)" so at least it seem to know the structure but I don't get any real Data from within the QMap.Now I kinda wonder how I am able to access the Data or if my try to make the List of Clients is completely wrong. I hope someone can help me with my problem.
And sorry for my bad English since it's not my native language, I hope you can understand my problem.
Thanks in Advance
-
@Throndar
To pass yourQMap
, define it as aQVariantMap
instead (which is just a typedef forQMap<QString, QVariant>
http://doc.qt.io/qt-5/qvariant.html#QVariantMap-typedefThen, there is an automatic conversion for it when passed to QML' JavaScript context.
http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#qvariantlist-and-qvariantmap-to-javascript-array-and-objectSo, you can get rid of your custom typedef altogether
typedef QMap<QString, MovieClient*> movies; //<-- NO LONGER NEEDED qRegisterMetaType<movies>("movies"); //<-- NOT NEEDED
and define
movieClients
asQVariantMap movieClients;
and
Q_PROPERTY(QVariantMap movieClientList MEMBER movieClients)
You must then fill your map as e.g.:
movieClients.insert("124.244.23.22", QVariant::fromValue(new MovieClient("client1", "v1", "124.244.23.22", 22, this)));
(the important part above is the conversion to
QVariant
).Now, you can access the data in JavaScript as:
var anObject = udpReceiver.movieClientList console.debug(Object.keys(anObject).length) for (var prop in anObject) { var client = anObject[prop] console.debug(client.name + " " + client.address + " " + client.port + " " + client.version) }
Good luck.
-
Hello @Diracsbracket ,
at first, I want to thank you very very much for your help. In QML I can reach the Variant List and this is really awesome :)
But now I have three smaller new problems:
- I create a JSON Document of all the Clients I received to fill a Tableview to be able to choose one from a List. This function also emitted a SIGNAL every time a new entry in the JSON was made.
With QMap I did this with a for-loop:
QJsonArray jClients; QString jCList; for(auto client : movieClients.toStdMap()) { QJsonObject cEntry; cEntry.insert("name", QJsonValue::fromVariant(client.second->name())); cEntry.insert("ipaddress", QJsonValue::fromVariant(client.second->address())); cEntry.insert("version", QJsonValue::fromVariant(client.second->version())); cEntry.insert("port", QJsonValue::fromVariant(client.second->port())); jClients.push_front(cEntry); } jCList = QJsonDocument(jClients).toJson(); emit jsonClientListUpdated(jCList);
It seems that I can't access the Properties this way. Is there a way to access the Values without the need of the key in the loop? (all the Answers I found online refered to QVariantMap["key"] but the key is set by the client responsing to a call, so i don't have that in the loop)
- When i start a new search, I have to clear the List of the exisiting Clients to make sure no Client is allready exisiting. I did this prior with
qDeleteAll(movieClients); movieClients.clear();
The QDeleteAll seems not to work with the QVariantMap since its no Pointer if I understood the Documentation correctly. Is there a safe way to clear out all the Items stored in the QVariantMap and Clear the QVariantMap itself?
- If i want to change some Values in the Objects with the UI, I tried to Change the Properties with the WRITE Q_Property specified in the Class of MovieClient
console.debug(client.name + " " + client.address + " " + client.port + " " + client.version) client.setName("ClientsNewName") console.debug(client.name + " " + client.address + " " + client.port + " " + client.version)
But this doesn't work this way. Is there a way to Change the Values of the Propertys after writing them to the QVariantMap or did I just forget something to Access them?
At the Moment I'm still wondering if it would be a good Idea to make a QVariantMap to show the Stats on the UI with QML and keep the old QMap for C++ to be able to call the set-Function to change Properties. But keeping two complete Sets of identical Data seems kinda a waste of resources.
Thanks in advance
- I create a JSON Document of all the Clients I received to fill a Tableview to be able to choose one from a List. This function also emitted a SIGNAL every time a new entry in the JSON was made.
-
@Throndar said in Access List of Objects in QML:
At the Moment I'm still wondering if it would be a good Idea to make a QVariantMap to show the Stats on the UI with QML and keep the old QMap for C++
I think you should consider storing your data in a model instead. This will make everything much simpler, including the interfacing between C++/QML and solve all of the problems you are currently facing.
Have a look at:
http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel-subclass -
Hello @Diracsbracket
thank you very much again for your help.
I just tried the last four hours to adept the animal example on the Link you provided to create a ClientModel but I still can't get it working in my Program.
I already tried the Youtube Tutorial Linked on the Documentation on Model in QML
https://www.youtube.com/watch?v=9BcAYDlpuT8&feature=youtu.be
this worked well but seemed to be used to populate a ListView (the only thing that I got to work on my Project without bigger problems with QJson)I also tried to work through the QT Documentation on http://doc.qt.io/qt-5/qabstractitemmodel.html but this got me even more confused since it States of a Model-index with rows and columns when I only try to build sets of simple Datatypes.
Perhaps somebody has some additional source of information / or an Example on how to create a model for data to be used in QML Context? Everything I found so far referred to a ListView instead of a List of Objects
I'm kinda wondering how hard it is to get a simple thing like a List of Objects with multiple Properties accessible in QML and would really appreciate additional Information to get me started.
If there is a better / easier Way to store Information about (multiple) Clients (each containing multiple QString, bool, int) in a Struct / Vector or some other way, I am open for all suggestions. The only requirement is to read and write the properties and be able to access them in QML.
Thanks in advance
-
@Throndar said in Access List of Objects in QML:
I still can't get it working in my Program.
Can you show us what you tried?
Post your basic test code (C++, QML andmain.cpp
) using dummy (i.e. hard-coded) DB data. That's the only way to see what's missing. -
Hello @Diracsbracket,
thanks again for your help. This is the Code for the first four Properties (if everything works each Client should get about 10-20 Properties).
At the Point of the Code below the manually added Clients are stored in the Model and the Listview shows them (the List is quite ugly, but all the Values are there).
The most important things that don't work are:
Since the MovieClientModel is defined in the main.cpp I have found no way to create MovieClients in my "ReceiverClass" and store them in the Model created in main.cpp (the Receiver gets the Response from the Clients and Creates a Client with the Data received) this Created Client have to be added to the model.The ListView shows the Data of the manual created Clients, but I still struggle to get Information back from the Model like "if there is a Client 3, show me all his Properties". I found no way to search the model for data contained in it.
The next important thing and I think it is connected to the one above, I have no idea how to access QProperies in MovieClient Objects or the Functions to set Properties (to Change the Name for Example).
And since the Listview is not Clickable I tried to use the Model in a TableView who showed all the Items quite nicely.
With the "onClicked" event if try to get the Information there I could console.log the row but again, no further data. I used the same method that worked on the List generated with JSON:onClicked: { console.log(row) // >> working, shows index console.log("Name: " + view2.model.get(row).name) // >> not working }
The Model and Model implementation i currently use:
the MovieClientModel:
#ifndef MOVIECLIENTMODEL_H #define MOVIECLIENTMODEL_H #include <QAbstractListModel> #include "movieclient.h" class MovieClientModel : public QAbstractListModel { Q_OBJECT public: enum MovieClientRoles { NameRole = Qt::UserRole, AddressRole = Qt::UserRole + 1, VersionRole = Qt::UserRole + 2, PortRole = Qt::UserRole + 3 }; MovieClientModel(QObject *parent = nullptr); void addMovieClient(const MovieClient &movieClient); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; protected: QHash<int, QByteArray> roleNames() const; private: QList<MovieClient> m_movieClients; }; #endif // MOVIECLIENTMODEL_H
the MovieClientModel.cpp
#include "movieclientmodel.h" MovieClientModel::MovieClientModel(QObject *parent) : QAbstractListModel (parent) { } void MovieClientModel::addMovieClient(const MovieClient &movieClient) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_movieClients << movieClient; endInsertRows(); } int MovieClientModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_movieClients.count(); } QVariant MovieClientModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= m_movieClients.count()) return QVariant(); const MovieClient &movieClient = m_movieClients[index.row()]; if(role == NameRole) return movieClient.name(); else if(role == AddressRole) return movieClient.address(); else if(role == VersionRole) return movieClient.version(); else if(role == PortRole) return movieClient.version(); } QHash<int, QByteArray> MovieClientModel::roleNames() const { QHash<int, QByteArray> roles; roles[NameRole] = "name"; roles[AddressRole] = "address"; roles[VersionRole] = "version"; roles[PortRole] = "port"; return roles; }
in the Main.cpp:
MovieClientModel model; model.addMovieClient(MovieClient("Client 1", "Version 1.2", "192.168.100.94", 2000)); model.addMovieClient(MovieClient("Client 2", "Version 1.2", "192.168.100.98", 5000)); model.addMovieClient(MovieClient("Client 3", "Version 1.2", "192.168.100.24", 1000)); engine.rootContext()->setContextProperty("MovieClientModel", &model);
and in QML:
Button { id: modelbutton anchors.centerIn: parent text: "modelTest" onClicked: { console.log(MovieClientModel.data(0, NameRole)) // >> not working, tried to Access data from the Model) } } ListView { id: movieClientModelList width: 200; height: 250 model: MovieClientModel delegate: Text { text: "MovieClient: " + name + ", " + version + ", " + address + ", " + port } }
-
@Throndar said in Access List of Objects in QML:
console.log(MovieClientModel.data(0, NameRole))
You are not using the correct arguments for the
data()
function.QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
0
is not aQModelIndex
object. You can get theModelIndex
via theindex(row, col, parent)
property, e.g.movieClientModel.index(0,0)
(Note that I renamed the instance by making it start with a lowercase char; the type name has uppercase first char.)
The C++ enum item
NameRole
is not directly accessible in QML: You must register it using theQ_ENUM
macro, but this requires you to register theMoveClientModel
type too.
http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#enumeration-typesWhen you do all the above, you can access the data as:
console.log(movieClientModel.data(movieClientModel.index(0,0), MovieClientModel.NameRole))
To avoid the hassle of the above syntax, I find it convenient to do this instead:
Add the following function to your modelQ_INVOKABLE QVariantMap get(int row);
and implement is as follows:
QVariantMap MovieClientModel::get(int row) { QVariantMap res; QModelIndex idx = index(row, 0); res["name"] = idx.data(NameRole); res["address"] = idx.data(AddressRole); res["version"] = idx.data(VersionRole); res["port"] = idx.data(PortRole); return res; }
The above will get you the complete model row, with fields now easily accessible via their names, so in QML you can now do:
console.log(movieClientModel.get(0).address)
Good luck!
-
Hello @Diracsbracket,
wow, I thank you so very much for your help.
With the new Function to get the Object of the Index, I can access all the Data from the Entry and start filling my QML the for displaying the Properties.I only have two question if you could help me again.
Is there a way to create MovieClients an add them to the MovieClientModel located in main from my Receiver class?
With the Test-MovieClients in the Main hardcoded build everything works fine, but since the answer from the Clients comes via TCP I let my Receiver-Class do the dynamic MovieClient-Creation till now.
In the Moment my Receiver-Class checks if there arrives Data on a specific address&port, parses it and if the message fits the expected message-definition from a Client it creates a client-object with the parameters received. Now I wonder how I can pass this MovieClient to the MovieClientModel on Creation.The other Question states the changing of a MovieClient.
I think this could be done with a set-method similar to the get you provided to me. Or do you think it's better to delete the existing entry and create a new client with the new properties? -
@Throndar said in Access List of Objects in QML:
Is there a way to create MovieClients an add them to the MovieClientModel located in main from my Receiver class?
Providing your
Receiver
object a reference to your model seems to be a straight-forward approach, no? Then you can invokeaddMovieClient()
from there.Or do you think it's better to delete the existing entry and create a new client with the new properties?
That's completely up to you, but my first approach would be to edit the model entry for a given client rather than to delete and recreate it.
-
@Diracsbracket said in Access List of Objects in QML:
Providing your
Receiver
object a reference to your model seems to be a straight-forward approach, no? Then you can invokeaddMovieClient()
from there.You are completely right (as always) and i really appreciate your help. At this spot, I am missing something. So far I used References for Function calls to let them work with the original Values. So I get what you intend to do.
Just like:
void myFunction(int &value){ value = 10; }
At first I tried to pass a reference to the MovieClientModel on the creation and changed to constructor on my
Receiver
, this resulted in all connect Statements to state "no matching function to call to connect".The next attempt was to set a "setModel(&ClientModelView)" Function, this resulted in the Error that MovieClientsModel in the
Receiver
cannot be assigned with "=" to the passed Value.Now I am completely unsure if I am at the right spot at all or if the passing of the Reference for Objects created in main.cpp is different from normal Functioncalls with a reference passed over.
-
Hello @Diracsbracket
if think you are right again.
I never needed to pass References to own Classes or Models till now. So used the usual call of:
UdpReceiver uReceiver(&model); with the Constructor of UdpReceiver(const MovieClientModel &model, QObject *parent = nullptr);
after checking the Code again it seemed the Reason for the Errors where the CopyConstructor and
Q_DECLARE_METATYPE
Macro I used, trying to get the original QMap working in QML.After removing these two the Call with the Reference to the MovieClientModel seem ok, now just the initialization of with the Reference make some problems.
Once again I thank you for your help and the Problems showed me that I need to clean up all the Stuff after trying something that didn't work and with the new AbstractModel I created with your tips i got huge step further.