listviewitem showing no data in QML application - c++

I have a classes.
dataitem
datamodel
interface
networkconnector
In dataitem is specified item properties.
class MDataItem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
Q_PROPERTY(QString service READ service WRITE setService NOTIFY serviceChanged)
Q_PROPERTY(QString status READ status WRITE setStatus NOTIFY statusChanged)
Q_PROPERTY(QString lastchck READ lastchck WRITE setLastChck NOTIFY lastchckChanged)
Q_PROPERTY(QString duration READ duration WRITE setDuration NOTIFY durationChanged)
Q_PROPERTY(QString info READ info WRITE setInfo NOTIFY infoChanged)
public:
explicit MDataItem(QObject *parent = 0);
QString host() const;
void setHost(const QString &newhost);
QString service() const;
void setService(const QString &newservice);
QString status() const;
void setStatus(const QString &newstatus);
QString lastchck() const;
void setLastChck(const QString &newlastchck);
QString duration() const;
void setDuration(const QString &newduration);
QString info() const;
void setInfo(const QString &newinfo);
signals:
void hostChanged();
void serviceChanged();
void statusChanged();
void lastchckChanged();
void durationChanged();
void infoChanged();
private:
QString m_host;
QString m_service;
QString m_status;
QString m_lastchck;
QString m_duration;
QString m_info;
};
In model is model specification
class MDataModel : public QAbstractTableModel
{
public:
enum Columns{
Host = Qt::UserRole,
Service = Qt::UserRole + 1,
Status = Qt::UserRole + 2,
Duration = Qt::UserRole + 3,
Info = Qt::UserRole + 4
};
MDataModel(QObject *parent = 0);
QHash<int, QByteArray> roleNames() const;
void fillData(QNetworkReply *r);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private:
QString dataAt(int offset) const;
QList<MDataItem*> items_;
public:
void prepareDataFinished(QNetworkReply *r);
};
The network get some data from the web.
class MNetworkConnector : public QObject
{
Q_OBJECT
public:
MNetworkConnector(QObject *parent=0);
private:
QNetworkAccessManager *manager;
void getData();
private slots:
void replyFinished(QNetworkReply *r);
void requireAuth(QNetworkReply *r, QAuthenticator *a);
signals:
void dataWasChanged(QNetworkReply *r);
};
and in the interface the network fill data in the model:
class MInterface : public QObject
{
Q_OBJECT
public:
MInterface();
MDataModel *mainModel;
MNetworkConnector *newConnection;
MDataModel* getModel();
public slots:
void dataWasPrepared(QNetworkReply *r);
};
At this point everything is Ok. In the debuger I see that data are in the model, in the correct MDataItem format.
But when I add model to the QML listView - application show nothing.
Where is the problem? Can you push me on the right way?
Main.cpp looks this:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MInterface *myIface = new MInterface();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("MainModel", myIface->getModel());
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
Main qml file:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQml 2.0
Window {
visible: true
width: Screen.width / 2
height: Screen.height / 2
title: qsTr("webinfo")
ListView
{
id: myList
visible: true
header: Header{}
model: MainModel
delegate: Item
{
Rectangle
{
Text { text: host }
}
}
}
}
When I set debuger breakpoint into my modedata class and in the function which should return the data, debuger not stop there. It looks that program never go inside this function.
Maybe problem is how I set model? Function getmodel() return pointer only.
main.cpp:
engine.rootContext()->setContextProperty("MainModel", myIface->getModel());
minterface.cpp:
MDataModel* MInterface::getModel()
{
return this->mainModel;
}
There is MDataModel.cpp
MDataModel::MDataModel(QObject *parent) : QAbstractTableModel(parent)
{
}
int MDataModel::rowCount(const QModelIndex & /* parent */) const
{
return items_.count();
}
int MDataModel::columnCount(const QModelIndex & /* parent */) const
{
return 5;
}
QVariant MDataModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
if (role == Qt::DisplayRole)
{
switch(index.column()){
case Columns::Host:
return items_.value(index.row())->host();
case Columns::Service:
return items_.value(index.row())->service();
case Columns::Status:
return items_.value(index.row())->status();
case Columns::Duration:
return items_.value(index.row())->duration();
case Columns::Info:
return items_.value(index.row())->info();
}
}
return QVariant();
}
QVariant MDataModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch(section){
case Columns::Host:
return tr("Host");
case Columns::Service:
return tr("Service");
case Columns::Status:
return tr("Status");
case Columns::Duration:
return tr("Duration");
case Columns::Info:
return tr("Info");
}
}
return QVariant();
}
void MDataModel::prepareDataFinished(QNetworkReply *r)
{
QList<MDataItem*> newItems;
MDataItem *item = new MDataItem();
QString data;
while(r->canReadLine())
{
data = r->readLine();
item = new MDataItem();
if(data.contains("warning", Qt::CaseInsensitive))
{
item->setStatus("WARNING");
}
else if(data.contains("critical", Qt::CaseInsensitive))
{
item->setStatus("CRITICAL");
}
else if(data.contains("unknown", Qt::CaseInsensitive))
{
item->setStatus("UNKNOWN");
}
item->setHost(reg.cap(0).replace("host=","").toUpper());
...
...
}
}
items_ = newItems;
qDebug() << items_;
}
QHash<int, QByteArray> MDataModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Columns::Host] = "host";
roles[Columns::Service] = "service";
roles[Columns::Status] = "status";
roles[Columns::Duration] = "duration";
roles[Columns::Info] = "info";
return roles;
}

Your main problems are:
MDataModel inherits QAbstractTableModel instead of QAbstractListModel. This is not an absolute problem since you could use a QAbstractTableModel, but since you are using a ListView in QML you should prefer using QAbstractListModel.
You are mixing roles and columns. This is a consequence of the first point since QAbstractListModel hides the concept of column.
You do not signal the model (and therefore the view/QML) you changed the data. Whenever changes happen to the underlying data you must tell the model by calling functions by pair, depending on what the change is: beginResetModel()/endResetModel(), beginInsertRows()/endInsertRows(), beginRemoveRows()/endRemoveRows(), etc. There are also 2 unpaired signals for that purpose: dataChanged() and modelReset(). Note that you can get away by always calling modelReset(), but this is not a good practice and you should favor using the most sepcific pair of function.
You should also read Qt documentation:
Using C++ Models with Qt Quick Views > QAbstractItemModel subclass
QAbstractItemModel Class > Protected Functions
And here is you fixed model. I removed columnCount() and headerData() as you do not need them to make it work.
MDataModel.h
class MDataModel : public QAbstractListModel
{
public:
enum Role{
Host = Qt::UserRole,
Service,
Status,
Duration,
Info
};
MDataModel(QObject *parent = 0);
QHash<int, QByteArray> roleNames() const;
void fillData(QNetworkReply *r);
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
private:
QString dataAt(int offset) const;
QList<MDataItem*> items_;
public:
void prepareDataFinished(QNetworkReply *r);
};
MDataModel.cpp
MDataModel::MDataModel(QObject *parent) : QAbstractListModel(parent)
{
}
int MDataModel::rowCount(const QModelIndex & /* parent */) const
{
return items_.count();
}
QVariant MDataModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
switch(role){
// For C++ views
case Qt::DisplayRole:
return items_.value(index.row())->host();
// For QML views
case Host:
return items_.value(index.row())->host();
case Service:
return items_.value(index.row())->service();
case Status:
return items_.value(index.row())->status();
case Duration:
return items_.value(index.row())->duration();
case Info:
return items_.value(index.row())->info();
}
return QVariant();
}
void MDataModel::prepareDataFinished(QNetworkReply *r)
{
QList<MDataItem*> newItems;
MDataItem *item = new MDataItem();
QString data;
while(r->canReadLine())
{
data = r->readLine();
item = new MDataItem();
if(data.contains("warning", Qt::CaseInsensitive))
{
item->setStatus("WARNING");
}
else if(data.contains("critical", Qt::CaseInsensitive))
{
item->setStatus("CRITICAL");
}
else if(data.contains("unknown", Qt::CaseInsensitive))
{
item->setStatus("UNKNOWN");
}
item->setHost(reg.cap(0).replace("host=","").toUpper());
...
...
}
}
// Call beginResetModel() and endResetModel() to tell the model
// and the view the data changed.
beginResetModel();
items_ = newItems;
endResetModel();
qDebug() << items_;
}
QHash<int, QByteArray> MDataModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Host] = "host";
roles[Service] = "service";
roles[Status] = "status";
roles[Duration] = "duration";
roles[Info] = "info";
// Add displayRole so that you can get what C++ views displays in QML
// e.g Text { text: host + ' ' + status + ' (' + displayRole +')' }
roles[Qt::DisplayRole] = "displayRole";
return roles;
}
QML fix
delegate: Component
{
Rectangle
{
width: 250
height: textItem.height
Text {
id: textItem
text: host + ' ' + status + ' (' + displayRole +')'
}
}
}

At this moment, my MDataModel.h and MDataModel.cpp looks like this:
MDataModel.h
class MDataModel : public QAbstractListModel
{
public:
enum icingaRoles{
host,
service,
status,
duration,
info
};
MDataModel(QObject *parent = 0);
QHash<int, QByteArray> roleNames() const;
void fillData(QNetworkReply *r);
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
private:
QString dataAt(int offset) const;
QList<MDataItem*> items_;
public:
void prepareDataFinished(QNetworkReply *r);
};
MDataModel.cpp
MDataModel::MDataModel(QObject *parent) : QAbstractListModel(parent)
{
}
int MDataModel::rowCount(const QModelIndex & /* parent */) const
{
return items_.count();
}
QVariant MDataModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
if (role == Qt::DisplayRole)
{
switch(index.column()){
case host:
return items_.value(index.row())->host();
case service:
return items_.value(index.row())->service();
case status:
return items_.value(index.row())->status();
case duration:
return items_.value(index.row())->duration();
case info:
return items_.value(index.row())->info();
}
}
return QVariant();
}
void MDataModel::prepareDataFinished(QNetworkReply *r)
{
QList<MDataItem*> newItems;
MDataItem *item = new MDataItem();
QString data;
while(r->canReadLine())
{
data = r->readLine();
QRegExp reg;
if(data.contains("statusbgwarning'><a href", Qt::CaseInsensitive) || data.contains("statusbgcritical'><a href", Qt::CaseInsensitive) || data.contains("statusbgunknown'><a href", Qt::CaseInsensitive))
{
item = new MDataItem();
newItems.append(item);
if(data.contains("warning", Qt::CaseInsensitive))
{
item->setStatus("WARNING");
}
else if(data.contains("critical", Qt::CaseInsensitive))
{
item->setStatus("CRITICAL");
}
else if(data.contains("unknown", Qt::CaseInsensitive))
{
item->setStatus("UNKNOWN");
}
...
...
}
// Call beginResetModel() and endResetModel() to tell the model
// and the view the data changed.
beginResetModel();
items_ = newItems;
endResetModel();
}
QHash<int, QByteArray> MDataModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[host] = "host";
roles[service] = "service";
roles[status] = "status";
roles[duration] = "duration";
roles[info] = "info";
return roles;
}

Related

QML ListView in qml is not updated when model changed

I am trying to update a model in a QML ListView from a cpp file..
Here is my implemenation. I copied evrything to this therfore it will be a little bit bigger from code size.
this is my model implementation albummodel.h
#include <QAbstractListModel>
#include <QStringList>
#include <QQmlListProperty>
class Album
{
public:
Album(const QString &type, const QString &size);
QString type() const;
QString size() const;
private:
QString m_type;
QString m_size;
};
class AlbumModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<QObject> myModel NOTIFY listChanged)
public:
enum AlbumRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AlbumModel(QObject *parent = 0);
//QQmlListProperty<QObject> getList();
void addAlbum(const Album &album);
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<Album> m_albums;
signals:
void listChanged();
#include <QAbstractListModel>
#include <QStringList>
#include <QQmlListProperty>
class Album
{
public:
Album(const QString &type, const QString &size);
QString type() const;
QString size() const;
private:
QString m_type;
QString m_size;
};
class AlbumModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<QObject> myModel NOTIFY listChanged)
public:
enum AlbumRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AlbumModel(QObject *parent = 0);
//QQmlListProperty<QObject> getList();
void addAlbum(const Album &album);
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<Album> m_albums;
signals:
void listChanged();
};
This is my albummodel.cpp
#include "albummodel_mediaplayer.h"
Album::Album(const QString &type, const QString &size)
: m_type(type), m_size(size)
{
}
QString Album::type() const
{
return m_type;
}
QString Album::size() const
{
return m_size;
}
AlbumModel::AlbumModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void AlbumModel::addAlbum(const Album &album)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_albums << album;
endInsertRows();
emit listChanged();
}
int AlbumModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_albums.count();
}
QVariant AlbumModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_albums.count())
return QVariant();
const Album &Album = m_albums[index.row()];
if (role == TypeRole)
return Album.type();
else if (role == SizeRole)
return Album.size();
return QVariant();
}
QHash<int, QByteArray> AlbumModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
/*QQmlListProperty<QObject> AlbumModel::getList(){
return QQmlListProperty<QObject>(this, *list);
}*/
and my qml file
import AlbumModel 1.0
ListView {
model: AlbumModel.myModel
delegate: playListThumbnail
Component {
id: playListThumbnail
Item {
Text {
id: albumTitleText
text: type//item_title
}}
....
}
I added the main Programm Code. Hope this is enough for a mvp
Main.cpp
void main() {
qmlRegisterType<AlbumModel>("AlbumModel",1,0,"AlbumModel");
Album g("test", "test");
AlbumModel h();
h.addAlbum(g);
}
But the model isn't updated. Which signal did i miss?

Implementing a QAbstractListModel subclass for QML ListView?

How can you implement the data function of QAbstractListModel such that it returns something with properties that are visible from a QML ListView's delegate?
For the item type, I tried implementing a QObject subclass that has Q_PROPERTYs, but QAbstractListModel::data returns QVariant, and a QObject* can't be converted to QVariant?
How do I create a QVariant that has named properties visible to QML?
class MyListItem : public QObject {
Q_OBJECT
Q_PROPERTY(type name READ name WRITE set_name NOTIFY nameChanged)
/*...*/
public:
MyListItem(QObject* parent, const QString& name) : QObject(parent) {
set_name(name);
}
};
class MyList : public QAbstractListModel
{
public:
MyList(QObject* parent);
Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVector<MyListItem*> items;
};
Q_INVOKABLE int MyList::rowCount(const QModelIndex &) const {
return items.size();
}
Q_INVOKABLE QVariant MyList::data(const QModelIndex &index, int) const {
MyListItem* item = items[index.row()];
return item; // <--- ERROR
}
Getting:
In member function ‘virtual QVariant MyList::data(const QModelIndex&, int) const’:
error: use of deleted function ‘QVariant::QVariant(void*)’
18 | return item;
| ^~~~
If you want to encapsulate a QObject in a QVariant then you must use QVariant::fromValue:
return QVariant::fromValue(item);
MWE:
main.cpp
#include <QAbstractListModel>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
class MyListItem : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE set_name NOTIFY nameChanged)
public:
MyListItem(const QString& name, QObject* parent=nullptr) : QObject(parent), m_name(name) {
}
QString name() const{
return m_name;
}
public slots:
void set_name(QString name){
if (m_name == name)
return;
m_name = name;
emit nameChanged(m_name);
}
signals:
void nameChanged(QString name);
private:
QString m_name;
};
class MyList : public QAbstractListModel
{
public:
enum MyListRoles {
ItemRole = Qt::UserRole + 1
};
MyList(QObject* parent=nullptr): QAbstractListModel(parent){}
~MyList(){
qDeleteAll(items);
items.clear();
}
void append(MyListItem *item){
beginInsertRows(QModelIndex(), rowCount(), rowCount());
items.append(item);
endInsertRows();
}
int rowCount(const QModelIndex &parent=QModelIndex()) const override{
if(parent.isValid())
return 0;
return items.count();
}
QVariant data(const QModelIndex &index, int role) const override{
if(!index.isValid())
return {};
if(index.row() <0 || index.row() >= rowCount())
return {};
if(role == ItemRole)
return QVariant::fromValue(items[index.row()]);
return {};
}
QHash<int, QByteArray> roleNames() const override{
return {{ItemRole, "item"}};
}
private:
QVector<MyListItem*> items;
};
#include "main.moc"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
MyList model;
model.append(new MyListItem("foo1"));
model.append(new MyListItem("foo2"));
model.append(new MyListItem("foo3"));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("mylist", &model);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
ListView{
anchors.fill: parent
model: mylist
delegate: Text {
text: model.item.name
}
}
}

QAbstractListModel::Data() method is never called

I am trying to view a list of c++ models in qml by subclassing QAbstractListModel following this tutorial Models and Views: AbstractItemModel Example
Here is my implementation:
Item model class
class User
{
private:
QString m_macAddr;
QString m_firstName;
QString m_lastName;
public:
User();
User(const QString &mac);
QString macAddr() const;
void setMacAddr(QString &mac);
};
User::User()
{}
User::User(const QString &mac){
m_macAddr = mac;
}
QString User::macAddr() const{
return m_macAddr;
}
void User::setMacAddr(QString &mac){
if(mac == m_macAddr)
return;
m_macAddr = mac;
// emit macAddrChanged();
}
QAbstractListModel subclass
class UserListModel : public QAbstractListModel
{
Q_OBJECT
public:
// User model roles
enum roles{
macRole = Qt::UserRole + 1
};
UserListModel(QObject *parent = nullptr);
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
QModelIndex getIndex(int r, int c, void *p);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void addUser(const User &user);
private:
QList<User> m_users;
protected:
QHash<int, QByteArray> rolesNames() const;
};
// Class inplementation.
UserListModel::UserListModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void UserListModel::addUser(const User &user){
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_users << user;
endInsertRows();
}
int UserListModel::rowCount(const QModelIndex &parent) const
{
return m_users.count();
}
QVariant UserListModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= rowCount())
return QVariant();
const User &user = m_users[index.row()];
if(role == macRole){
return user.macAddr();
}
return QVariant();
}
QModelIndex UserListModel::index(int row, int column, const QModelIndex &parent) const
{
return parent;
}
QModelIndex UserListModel::parent(const QModelIndex &index) const
{
return index;
}
QModelIndex UserListModel::getIndex(int r, int c, void *p) {
return this->createIndex(r, c, p);
}
QHash<int, QByteArray> UserListModel::rolesNames() const {
QHash<int, QByteArray> roles;
roles[macRole] = "mac";
return roles;
}
main.cpp
QQmlApplicationEngine engine;
QString mac = "12:3e:3w:4r:33";
userlistModel->addUser(User(mac));
userlistModel->addUser(User(mac));
userlistModel->addUser(User(mac));
userlistModel->addUser(User(mac));
engine.rootContext()->setContextProperty("usersModel", userlistModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QModelIndex id = userlistModel->getIndex(1, 0, 0);
QVariant v1 = userlistModel->data(id, Qt::UserRole + 1);
qDebug() << "===========" << v1.toString();
controller::DatabaseService<SqliteDB> *sqliteDb = new controller::DatabaseService<SqliteDB>();
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
QML
ListView{
id: listView
anchors.fill: parent
model:usersModel
delegate: Component { HorizontalListItem{mac: mac}}
}
The Problem is :
In main function I am adding 4 models to the list model but, They are shown in qml without data. althought, I am trying to view mac role.
what I tried
I tried to add a breakpoint inside methodes rolesNames() and data()
but, It seems that the compiler is not getting inside anyone of them.
I tried to invoke data() method in main function and it returns the desired data.
It is not necessary that you override the index, or parent methods, nor do you create the getIndex method. In your case you need to implement the roleNames method:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QAbstractListModel>
#include <QDebug>
class User
{
QString m_macAddr;
public:
User(){}
User(const QString &mac): m_macAddr(mac){}
QString macAddr() const{return m_macAddr;}
void setMacAddr(QString &mac){m_macAddr = mac;}
};
class UserListModel : public QAbstractListModel
{
public:
enum roles{
macRole = Qt::UserRole + 1
};
explicit UserListModel(QObject *parent = nullptr)
: QAbstractListModel(parent){}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
if (parent.isValid())
return 0;
return m_users.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (!index.isValid())
return QVariant();
if (index.row() < 0 || index.row() >= rowCount())
return QVariant();
const User &user = m_users[index.row()];
if(role == macRole)
return user.macAddr();
return QVariant();
}
QHash<int, QByteArray> roleNames() const override{
QHash<int, QByteArray> roles;
roles[macRole] = "mac";
return roles;
}
void addUser(const User &user){
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_users << user;
endInsertRows();
}
private:
QList<User> m_users;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
UserListModel userlistModel;
QString mac = "12:3e:3w:4r:33";
userlistModel.addUser(User(mac));
userlistModel.addUser(User(mac));
userlistModel.addUser(User(mac));
userlistModel.addUser(User(mac));
QModelIndex ix = userlistModel.index(1, 0);
QVariant v = userlistModel.data(ix, UserListModel::macRole);
qDebug() << "===========" << v.toString();
engine.rootContext()->setContextProperty("usersModel", &userlistModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListView{
anchors.fill: parent
model:usersModel
delegate: Component
{
Text{
text: model.mac
}
}
}
}

Unknown method return type even if declared

Qt5.11 / QML. I'm trying to create a two-level QAbstractListModel to be viewed in QML. Think as a folders (first-level) and files (second-level) model.
Here my code:
model.h
class File
{
public:
File(const QString name) { _name = name; }
QString name() const { return _name; }
void setName(const QString &name) { _name = name; }
QPixmap thumbnail() const { return _thumbnail; }
void setThumbnail(QPixmap thumbnail) { _thumbnail = thumbnail; }
private:
QString _name;
QPixmap _thumbnail;
};
class FilesModel : public QAbstractListModel
{
Q_OBJECT
public:
enum FileRoles
{
NameRole = Qt::UserRole + 1,
ThumbnailRole
};
FilesModel(QObject *parent = nullptr);
void addFile(const File &file);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QString _name;
QList<File> _listFiles;
};
class Folder
{
public:
Folder(const QString name);
QString name() const { return _name; }
void setName(const QString &name) { _name = name; }
FilesModel *model() { return _model; }
private:
QString _name;
FilesModel *_model;
};
class FolderModel : public QAbstractListModel
{
Q_OBJECT
public:
enum FolderRoles
{
NameRole = Qt::UserRole + 1,
ModelRole
};
FolderModel(QObject *parent = nullptr);
void addFolder(const Folder &folder);
void clear();
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;
Folder *findFolder(QString name);
FilesModel *filesModel(QString name);
protected:
QHash<int, QByteArray> roleNames() const;
bool removeRows(int row, int count, const QModelIndex &parent);
private:
QString _name;
QList<Folder> _list;
};
Q_DECLARE_METATYPE(FilesModel*)
model.cpp
FilesModel::FilesModel(QObject *parent) : QAbstractListModel(parent) { }
void FilesModel::addFile(const File &file)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_listFiles << file;
endInsertRows();
}
int FilesModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return _listFiles.count();
}
QVariant FilesModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= _listFiles.count()) return QVariant();
const File &item = _listFiles[index.row()];
switch (role) {
case NameRole: return item.name(); break;
case ThumbnailRole: return item.thumbnail(); break;
default: return QVariant();
}
}
QHash<int, QByteArray> FilesModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[ThumbnailRole] = "thumbnail";
return roles;
}
FolderModel::FolderModel(QObject *parent) : QAbstractListModel(parent) { }
void FolderModel::addFolder(const Folder &folder)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_list << folder;
endInsertRows();
}
bool FolderModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (count == 0) return false;
beginRemoveRows(parent, row, row + count - 1);
for (int i = 0; i < count; i++) _list.removeAt(row);
endRemoveRows();
emit dataChanged(this->index(row, 0), this->index(row, 0));
return true;
}
void FolderModel::clear()
{
removeRows(0, rowCount(), QModelIndex());
}
int FolderModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return _list.count();
}
QVariant FolderModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= _list.count()) return QVariant();
const Folder &item = _list[index.row()];
switch (role) {
case NameRole: return item.name(); break;
default: return QVariant();
}
}
Folder *FolderModel::findFolder(QString name)
{
for (int i = 0; i < _list.size(); i++)
{
if (_list[i].name() == name) return &_list[i];
}
return NULL;
}
FilesModel *FolderModel::filesModel(QString name)
{
Folder *folder = findFolder(name);
if (folder)
{
FilesModel *model = folder->model();
QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
return model;
}
return NULL;
}
QHash<int, QByteArray> FolderModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[ModelRole] = "model";
return roles;
}
Folder::Folder(const QString name) : _name(name)
{
_model = new FilesModel();
}
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MyApp myapp;
QQmlApplicationEngine qmlEngine;
qmlEngine.rootContext()->setContextProperty("MyModel", myapp.model());
qmlEngine.rootContext()->setContextProperty("MyApp", &myapp);
qmlEngine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (qmlEngine.rootObjects().isEmpty()) return -1;
return app.exec();
}
in MyApp.cpp I populate some items:
FolderModel _model;
_model.addFolder(Folder("Folder1"));
_model.addFolder(Folder("Folder2"));
// ...
Folder *objFolder = _model.findFolder(folder);
objFolder->model()->addFile(File("File 1"));
objFolder->model()->addFile(File("File 2"));
The QML code is mainly based upon the Photo Viewer demo.
main.qml
ApplicationWindow {
id: mainWindow
visible: true
DelegateModel { id: albumVisualModel; model: MyModel; delegate: AlbumDelegate {} }
GridView {
id: albumView
width: parent.width
height: parent.height
cellWidth: 210
cellHeight: 220
model: albumVisualModel.parts.album
visible: albumsShade.opacity !== 1.0
}
Rectangle {
id: albumsShade; color: mainWindow.color
width: parent.width; height: parent.height; opacity: 0.0
}
ListView {
anchors.fill: parent
model: albumVisualModel.parts.browser
interactive: false
}
}
AlbumDelegate.qml (first part)
Component {
id: albumDelegate
Package {
Item {
Package.name: 'browser'
GridView {
id: photosGridView; model: visualModel.parts.grid; width: mainWindow.width; height: mainWindow.height - 21
x: 0; y: 21; cellWidth: 160; cellHeight: 153; interactive: false
onCurrentIndexChanged: photosListView.positionViewAtIndex(currentIndex, ListView.Contain)
}
}
Item {
Package.name: 'fullscreen'
ListView {
id: photosListView; model: visualModel.parts.list; orientation: Qt.Horizontal
width: mainWindow.width; height: mainWindow.height; interactive: false
onCurrentIndexChanged: photosGridView.positionViewAtIndex(currentIndex, GridView.Contain)
highlightRangeMode: ListView.StrictlyEnforceRange; snapMode: ListView.SnapOneItem
}
}
Item {
Package.name: 'album'
id: albumWrapper; width: 210; height: 220
DelegateModel {
property string tag
id: visualModel; delegate: PhotoDelegate { }
model: MyApp.filesModel(tag)
}
It correctly shows the first-level model (folders) but it fails when I try to show the files in one of them with the following error:
qrc:/AlbumDelegate.qml:36: Error: Unknown method return type: FilesModel*
But I've declared that type using Q_DECLARE_METATYPE.
Do I need to add something else?
As far as I know I don't need qRegisterMetaType() because I'm not using it in signals or slots.

Qt: QListView with QAbstractItemModel and QStyledItemDelegate takes long for adding items

I have a qt-application with QListView, a custom model and a class inherited from QStyledItemDelegate to show custom items. Unfortunately it takes to long (5 seconds) when I add approximately 6000 items. Do you have a hint/solution for my problem?
Below you see my implementation:
The Model:
class AddressItemData : public QObject {
Q_OBJECT
public:
AddressItemData (QObject * parent = 0) : QObject(parent) {}
QString address;
QString name;
bool inserted;
};
class AddressModel : public QAbstractListModel
{
Q_OBJECT
public:
AddressModel (QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const {
return addressItems.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (!index.isValid())
return QVariant();
if (index.row() >= addressItems.size() || index.row() < 0)
return QVariant();
if (Qt::DisplayRole == role) {
return QVariant::fromValue<AddressItemData*>(addressItems.at(index.row()));
}
return QVariant();
}
void addAddressItem(AddressItemData* addressItem){
beginInsertRows(QModelIndex(), rowCount(), rowCount());
addressItems.append(addressItem);
endInsertRows();
}
void addAddressItems(QList<AddressItemData*> addressItems){
beginInsertRows(QModelIndex(), rowCount(), rowCount() + addressItems.size() - 1);
this->addressItems.append(addressItems);
endInsertRows();
}
void removeAddressItem(AddressItemData* addressItem);
void deleteAllAddressItems(void){
beginRemoveRows(QModelIndex(), rowCount(), rowCount());
for (int i = 0; i < addressItems.size(); ++i) {
delete addressItems.at(i);
}
addressItems.clear();
endRemoveRows();
}
private:
QList<AddressItemData*> addressItems;
};
For the model I created a custom add and delete method, which will be called from the delegate.
The ItemViewDelegate:
class AddressViewDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
AddressViewDelegate (QWidget *parent = 0) : QStyledItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
if(!index.isValid())
return;
painter->save();
QStyleOptionViewItemV4 opt = option;
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
QRect addressRect = opt.rect;
AddressItemData* d = index.data().value<AddressItemData*>();
painter->setPen(Qt::black);
painter->drawText(QRect(addressRect.left(), addressRect.top(), addressRect.width(), addressRect.height()/2),
opt.displayAlignment, d->address);
}
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const {
if(!index.isValid())
return QSize();
QSize result = QStyledItemDelegate::sizeHint(option, index);
result.setHeight(result.height()*2);
return result;
}
};
For test purposes, the application displays only the address. I want to show also the other information next to address in one line. Later I also want to add signal/slot-behavior for each row, but that's still a long way off.
The main:
AddressModel* model = new AddressModel;
AddressViewDelegate* viewDelegate = new AddressViewDelegate;
ui->listView->setItemDelegate(viewDelegate);
ui->listView->setModel(model);
ui->listView->setUniformItemSizes(true);
ui->listView->setLayoutMode(QListView::Batched);
QList<AddressItemData *> addressItems;
... for loop to add AddressItemData:
AddressItemData* data = new AddressItemData;
data->address = anotherTable.at(index);
addressItems.append(data);
// ...
model->addAddressItems(addressItems);
In the main, I call addAddressItems but also with the single-call-variant it is slow. I hope my code is clear enough and I can help others as well.
Do you have an idea, why the adding-procedure is so slow?