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
}
}
}
}
Related
I have implemented QAbstractItemModel as a C++ class and I'm trying to use it as a model for QComboBox. The model is storing QList of QStrings, which are names of project apps INI files. I can print out the list into console, and I'am certainly getting the right list of files. However I'm getting an error Unable to assign [undefined] to QString.
Can you please help me, what am I doing wrong?
LcuModel.cpp
#include "LcuModel.h"
#include "logging/LoggingQtCategories.h"
#include <QCoreApplication>
#include <QSettings>
#include <QFileInfo>
#include <QDir>
LcuModel::LcuModel(QObject *parent)
: QAbstractItemModel(parent)
{
QSettings settings;
QFileInfo fileInfo(settings.fileName());
QDir directory(fileInfo.absolutePath());
iniFiles_ = directory.entryList({"*.ini"},QDir::Files);
if (iniFiles_.empty())
{
qCWarning(lcLcu) << "No ini files were found.";
}
qCInfo(lcLcu) << iniFiles_; //this prints out all the files to console, so the list is certainly not empty
}
QModelIndex LcuModel::index(int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(row)
Q_UNUSED(column)
Q_UNUSED(parent)
return QModelIndex();
}
QModelIndex LcuModel::parent(const QModelIndex &index) const
{
Q_UNUSED(index)
return QModelIndex();
}
int LcuModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent)
return iniFiles_.count();
}
int LcuModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant LcuModel::data(const QModelIndex & index, int role) const
{
Q_UNUSED(role);
int row = index.row();
qCInfo(lcLcu) << QString::number(row) << " ";
if (row < 0 || row >= iniFiles_.count()) {
return QVariant();
}
return QVariant(iniFiles_.at( index.row() ));
}
QHash<int, QByteArray> LcuModel::roleNames() const
{
QHash<int, QByteArray> names;
names[textRole] = "fileName";
return names;
}
LcuModel.h
#ifndef LCUMODEL_H
#define LCUMODEL_H
#include <QAbstractItemModel>
class LcuModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit LcuModel(QObject *parent = nullptr);
enum {
textRole
};
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
protected:
virtual QHash<int, QByteArray> roleNames() const override;
private:
QList<QString> iniFiles_;
};
#endif // LCUMODEL_H
LcuMain.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import project.lcu 1.0
Item {
id: lcuMain
LcuModel{
id:lcuModel
}
Component.onCompleted: {
console.info("Completed "+ comboBox.count + "items")
} //this gives me right number of items from the list
Column{
ComboBox{
id: comboBox
model: lcuModel
delegate: ItemDelegate
{
contentItem: Text {
text: fileName
}
}
onActivated: console.info(comboBox.currentText + comboBox.currentIndex) //it prints out an empty string
}
}
}
console output
18:02:25.543 [ info ] project.lcu: LcuModel::LcuModel - ("first.ini", "second.ini", "third.ini", "fourth.ini", "fifth.ini", "sixth.ini", "seventh.ini", "eight.ini", "nineth.ini")
18:02:25.616 [warning] unknown - ComboBox.qml:47:5: QML Connections: Cannot assign to non-existent property "onCountChanged"
18:02:25.675 [ info ] qml: onCompleted - 9
18:02:25.678 [warning] unknown - qrc:/LcuMain.qml:46:25: Unable to assign [undefined] to QString
It is unnecessary for the model to inherit from QAbstractItemModel, QAbstractListModel is enough so you don't have to implement multiple methods. On the other hand, you must establish the role that you want to show through textRole and in the delegate you must use modelData:
#ifndef LCUMODEL_H
#define LCUMODEL_H
#include <QAbstractListModel>
class LcuModel : public QAbstractListModel
{
Q_OBJECT
public:
enum {
textRole = Qt::UserRole
};
explicit LcuModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
private:
QStringList iniFiles_;
};
#endif // LCUMODEL_H
#include "lcumodel.h"
#include <QDir>
#include <QFileInfo>
#include <QSettings>
LcuModel::LcuModel(QObject *parent)
: QAbstractListModel(parent)
{
QSettings settings;
QFileInfo fileInfo(settings.fileName());
QDir directory(fileInfo.absolutePath());
iniFiles_ = directory.entryList({"*.ini"}, QDir::Files);
}
int LcuModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return iniFiles_.length();
}
QVariant LcuModel::data(const QModelIndex &index, int role) const
{
if(!checkIndex(index)){
return QVariant();
}
if(role == textRole){
return iniFiles_.at(index.row());
}
return QVariant();
}
QHash<int, QByteArray> LcuModel::roleNames() const
{
QHash<int, QByteArray> names;
names[textRole] = "fileName";
return names;
}
import QtQuick 2.12
import QtQuick.Controls 2.12
import project.lcu 1.0
Item{
id: lcuMain
LcuModel{
id:lcuModel
}
Column{
ComboBox{
id: comboBox
model: lcuModel
textRole: "fileName"
delegate: ItemDelegate{
text: modelData
width: parent.width
}
}
}
}
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?
I'm trying to show a simple XML file in a QML treeView through the QAbstractItemModel class, but I can't find any decent tutorial on how to do so online. The best I managed to find is a tutorial about how to do it in QWidget, but I want to do it in QML. I tried anyway with all the bits I could find here and there but couldn't even manage to get a tree to show up.
Code compiles, but my model is null. I'm not even sure if I'm actually reading my XML file.
There's very likely a lot of things I am missing or doing wrong. Can anyone help ?
DomItem.h
#ifndef DOMITEM_H
#define DOMITEM_H
#include <QDomNode>
#include <QHash>
class DomItem
{
public:
DomItem(const QDomNode &node, int row, DomItem *parent = nullptr);
~DomItem();
DomItem *child(int i);
DomItem *parent();
QDomNode node() const;
int row() const;
private:
QDomNode domNode;
QHash<int, DomItem *> childItems;
DomItem *parentItem;
int rowNumber;
};
#endif // DOMITEM_H
DomItem.cpp
#include "DomItem.h"
DomItem::DomItem(const QDomNode &node, int row, DomItem *parent)
: domNode(node),
parentItem(parent),
rowNumber(row)
{
}
DomItem::~DomItem()
{
qDeleteAll(childItems);
}
DomItem *DomItem::parent()
{
return parentItem;
}
int DomItem::row() const
{
return rowNumber;
}
QDomNode DomItem::node() const
{
return domNode;
}
DomItem *DomItem::child(int i)
{
DomItem *childItem = childItems.value(i);
if (childItem)
return childItem;
// if child does not yet exist, create it
if (i >= 0 && i < domNode.childNodes().count()) {
QDomNode childNode = domNode.childNodes().item(i);
childItem = new DomItem(childNode, i, this);
childItems[i] = childItem;
}
return childItem;
}
DomModel.h
#ifndef DOMMODEL_H
#define DOMMODEL_H
#include <QObject>
#include <QModelIndex>
#include <QAbstractItemModel>
#include <QDomDocument>
#include <QFile>
#include "DomItem.h"
class DomModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum DomModelRoles
{
DomModelRoleName = Qt::UserRole + 1,
DomModelRoleAttributes,
DomModelRoleValue
};
explicit DomModel(const QDomDocument &document, QObject *parent = nullptr);
~DomModel();
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
void loadXml(QString xmlFilePath);
void reload();
QHash<int, QByteArray> roleNames() const override;
private:
QDomDocument domDocument;
DomItem *rootItem;
QString xmlSource;
};
#endif // DOMMODEL_H
DomModel.cpp
#include "DomModel.h"
DomModel::DomModel(const QDomDocument &document, QObject *parent)
: QAbstractItemModel(parent),
domDocument(document)
{
rootItem = new DomItem(domDocument, 0);
}
DomModel::~DomModel()
{
delete rootItem;
}
int DomModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 3;
}
Qt::ItemFlags DomModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return QAbstractItemModel::flags(index);
}
QVariant DomModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case 0:
return tr("Name");
case 1:
return tr("Attributes");
case 2:
return tr("Value");
default:
break;
}
}
return QVariant();
}
QModelIndex DomModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
DomItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<DomItem*>(parent.internalPointer());
DomItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
return QModelIndex();
}
int DomModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
DomItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<DomItem*>(parent.internalPointer());
return parentItem->node().childNodes().count();
}
QModelIndex DomModel::parent(const QModelIndex &child) const
{
if (!child.isValid())
return QModelIndex();
DomItem *childItem = static_cast<DomItem*>(child.internalPointer());
DomItem *parentItem = childItem->parent();
if (!parentItem || parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
QVariant DomModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
const DomItem *item = static_cast<DomItem*>(index.internalPointer());
const QDomNode node = item->node();
switch (role)
{
case DomModelRoleName:
return node.nodeName();
case DomModelRoleAttributes:
{
const QDomNamedNodeMap attributeMap = node.attributes();
QStringList attributes;
for (int i = 0; i < attributeMap.count(); ++i) {
QDomNode attribute = attributeMap.item(i);
attributes << attribute.nodeName() + "=\""
+ attribute.nodeValue() + '"';
}
return attributes.join(' ');
}
case DomModelRoleValue:
return node.nodeValue().split('\n').join(' ');
default:
break;
}
return QVariant();
}
void DomModel::loadXml(QString xmlFilePath)
{
xmlFilePath.replace("file:///","");
// on parse le xml de l'environnement
QFile file(xmlFilePath);
if (!file.open(QIODevice::ReadOnly| QFile::Text)){
//QMessageBox::critical(this, "ParserFichierXML", "Error");
}
else{
domDocument.clear();
if (!domDocument.setContent(&file)) {
//QMessageBox::critical(this, "ParserFichierXML", "Error");
}
file.close();
}
reload();
}
void DomModel::reload()
{
beginResetModel();
delete rootItem;
rootItem = new DomItem(domDocument, 0,false);
endResetModel();
}
QHash<int, QByteArray> DomModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[DomModelRoleName] = "Name";
roles[DomModelRoleAttributes] = "Attributes";
roles[DomModelRoleValue] = "Value";
return roles;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDomDocument>
#include <QQmlContext>
#include "DomModel.h"
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);
QQmlApplicationEngine engine;
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);
QQmlContext *ctx = engine.rootContext();
QFile file("./test.xml");
if (file.open(QIODevice::ReadOnly))
{
QDomDocument document;
if (document.setContent(&file))
{
DomModel *model = new DomModel(document);
ctx->setContextProperty("theModel", model);
}
}
file.close();
engine.load(url);
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import QtLocation 5.3
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtQml 2.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
TreeView
{
id:treeView
anchors.fill: parent
model: theModel
itemDelegate: TreeDelegate
{
id: treeDeleg
}
style: TreeViewStyle
{
id: styleTree
}
TableViewColumn
{
id:column1
role: "Name"
title: "Name"
width: 450
}
TableViewColumn
{
id:column3
role: "Value"
title: "Value"
width: 400
}
Component.onCompleted:
{
treeView.model = Qt.binding(function()
{
if (typeof theModel !== "undefined")
{
console.log("Model loaded");
return theModel;
}
else
{
console.log("Couldn't load model");
return null;
}
});
}
}
}
TreeDelegate.qml
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQuick 2.7
import QtQml 2.0
Item
{
id: item
width : parent.width
height: 25
TextEdit
{
id: text1
font.pixelSize: 14
readOnly: true
focus: false
width : parent.width
height: 25 * lineCount
anchors.left: parent.left
wrapMode: TextEdit.Wrap
text: styleData.value
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
}
}
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
}
}
}
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;
}