Cannot refresh QT's view while changing model - qt

I am learning how to integrate qml with c++.
I've implemented a custom model class StringListModel, which inherits QAbstratListModel.
And, I have a main.qml to use StringListModel.
QML view can show initial values correctly.
I have another thread to change model periodically.
I do use beginResetModel() and endResetModel() to indicate model changed.
However, while the model keeps been changed, the view didn't update.
Here is my source code.
Please teach me what went wrong.
THANKS!
=== main.qml ===
Rectangle {
width: 360
height: 360
Grid {
id: gridview
columns: 2
spacing: 20
Repeater {
id: repeater
model: StringListModel {
id:myclass
}
delegate: Text {
text: model.title
}
}
}
=== custom class.h ===
class StringListModel : public QAbstractListModel{
Q_OBJECT
public:
StringListModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QHash<int, QByteArray> roleNames() const;
void newItem();
private:
QStringList stringList;
};
class UpdateThread : public QThread {
Q_OBJECT
StringListModel *mMyClass;
public:
UpdateThread(StringListModel * myClass);
protected:
void run();
};
=== custom class.cpp ===
StringListModel::StringListModel(QObject *parent) : QAbstractListModel(parent)
{
stringList << "one" << "two" << "three";
QThread *thread = new UpdateThread(this);
thread->start();
}
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::UserRole + 1)
return stringList.at(index.row());
else
return QVariant();
}
QHash<int, QByteArray> StringListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole + 1] = "title";
return roles;
}
void StringListModel::newItem()
{
qDebug() << "size: " << stringList.size();
beginResetModel();
stringList << "new";
endResetModel();
}
UpdateThread::UpdateThread(StringListModel * myClass)
{
mMyClass = myClass;
}
void UpdateThread::run()
{
while (true) {
mMyClass->newItem();
msleep(1000);
}
}

You're dutifully ignoring the matters of synchronizing access to the model. When you have any object that's accessed from more than one thread (even something as "simple" as a raw pointer), you must deal with the ramifications of such access.
I suggest you don't mess with threads unless you have measurements showing that you'll benefit.

Related

Creating listview from QQuickItem in C++

I am trying to make a listview component using QQuickItem and load its model using QAbstractListModel. Below are the steps which i tried.
listviewComponent.qml
ListView {
required model
delegate: Text {
required property string type
required property string size
text: type + ", " + size
}
}
Model.h
class Model
{
public:
Model(const QString& type, const QString& size);
QString type() const;
QString size() const;
private:
QString m_type;
QString m_size;
};
class CustomModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
CustomModel(QObject* parent = 0);
void addElement(const Model& pElement);
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<Model> m_list;
};
Model.cpp
Model::Model(const QString& type, const QString& size)
: m_type(type), m_size(size)
{
}
QString Model::type() const
{
return m_type;
}
QString Model::size() const
{
return m_size;
}
CustomModel::CustomModel(QObject* parent)
: QAbstractListModel(parent)
{
}
void CustomModel::addElement(const Model &pElement)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_list << pElement;
endInsertRows();
}
int CustomModel::rowCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return m_list.count();
}
QVariant CustomModel::data(const QModelIndex& index, int role) const {
if (index.row() < 0 || index.row() >= m_list.count())
return QVariant();
const Model& mod = m_list[index.row()];
if (role == TypeRole)
return mod.type();
else if (role == SizeRole)
return mod.size();
return QVariant();
}
QHash<int, QByteArray> CustomModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
Myclass.h
class myclass : public QObject
{
Q_OBJECT
public:
myclass();
inline int createUI(QQmlApplicationEngine &engine){
QQuickWindow *window = qobject_cast<QQuickWindow*>(engine.rootObjects().at(0));
if (!window) {
qFatal("Error: Your root item has to be a window.");
return -1;
}
QQuickItem *root = window->contentItem();
window->setWidth(600);
window->setHeight(500);
QQmlComponent listviewComp(&engine, QUrl(QStringLiteral("qrc:/listviewComponent.qml")));
CustomModel model;
model.addElement(Model("Wolf", "Medium"));
model.addElement(Model("Polar bear", "Large"));
model.addElement(Model("Quoll", "Small"));
QQuickItem *listview = qobject_cast<QQuickItem*>(listviewComp.createWithInitialProperties({ {"model", QVariant::fromValue(&model)} }));
QQmlEngine::setObjectOwnership(listview, QQmlEngine::CppOwnership);
listview->setParentItem(root);
listview->setParent(&engine);
listview->setProperty("objectName", "lv");
listview->setWidth(200);
listview->setHeight(100);
listview->setX(250);
listview->setY(30);
window->show();
return 0;
}
};
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine.load(url);
QQmlContext *item = engine.rootContext();
myclass myClass;
item->setContextProperty("_myClass", &myClass);
myClass.createUI(engine);
return app.exec();
}
Problem: Listview is getting displayed but with only one row, but there are 3 rows added in CustomModel. I am assuming some problem with createWithInitialProperties, but couldnt able to crack it.
The problem is caused because "model" is a local variable that will be destroyed as soon as createUI is finished executing, and what you see as the first row is just a cache, you shouldn't actually see a row (it looks like a bug). The solution is to use the heap memory:
// ...
CustomModel *model = new CustomModel(window);
model->addElement(Model("Wolf", "Medium"));
model->addElement(Model("Polar bear", "Large"));
model->addElement(Model("Quoll", "Small"));
QQuickItem *listview = qobject_cast<QQuickItem*>(listviewComp.createWithInitialProperties({ {"model", QVariant::fromValue(model)} }));
// ...

how to port a QAbstractItemModel to QtQuick (without defining ItemDelegate)

I have a QAbstractItemModel tree model for a Qt3D Entity tree:
qt3dentitytreemodel.h:
#define QT3DENTITYTREEMODEL_H
#include <QAbstractItemModel>
#include <Qt3DCore/QEntity>
class Qt3DEntityTreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_PROPERTY(Qt3DCore::QEntity * rootEntity READ rootEntity WRITE setRootEntity NOTIFY rootEntityChanged)
explicit Qt3DEntityTreeModel(QObject *parent = nullptr);
void setRootEntity(Qt3DCore::QEntity *rootEntity);
Qt3DCore::QEntity * rootEntity() const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
signals:
void rootEntityChanged(Qt3DCore::QEntity *rootEntity);
private:
Qt3DCore::QEntity *rootEntity_;
};
#endif // QT3DENTITYTREEMODEL_H
qt3dentitytreemodel.cpp:
#include "qt3dentitytreemodel.h"
Qt3DEntityTreeModel::Qt3DEntityTreeModel(QObject *parent)
: QAbstractItemModel(parent)
{
}
void Qt3DEntityTreeModel::setRootEntity(Qt3DCore::QEntity *rootEntity)
{
qDebug() << "Qt3DEntityTreeModel::setRootEntity" << rootEntity;
auto old = rootEntity_;
rootEntity_ = rootEntity;
if(rootEntity_ != old)
emit rootEntityChanged(rootEntity);
}
Qt3DCore::QEntity * Qt3DEntityTreeModel::rootEntity() const
{
return rootEntity_;
}
QModelIndex Qt3DEntityTreeModel::index(int row, int column, const QModelIndex &parent) const
{
if(!rootEntity_) return {};
if(column != 0) return {};
if(!parent.isValid())
{
if(row == 0) return createIndex(0, 0, rootEntity_);
}
else
{
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(parent.internalPointer());
if(!entity) return {};
return createIndex(row, column, entity->childNodes().at(row));
}
return {};
}
QModelIndex Qt3DEntityTreeModel::parent(const QModelIndex &child) const
{
if(!rootEntity_) return {};
if(!child.isValid()) return {};
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(child.internalPointer());
if(!entity) return {};
auto parent = entity->parentNode();
if(!parent) return {};
auto grandParent = parent->parentNode();
if(!grandParent) return createIndex(0, 0, parent);
return createIndex(grandParent->childNodes().indexOf(parent), 0, parent);
}
QVariant Qt3DEntityTreeModel::data(const QModelIndex &index, int role) const
{
if(!rootEntity_) return {};
if(!index.isValid()) return {};
if(role != Qt::DisplayRole && role != DisplayRole) return {};
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(index.internalPointer());
if(!entity) return {};
QString data = entity->metaObject()->className();
if(!entity->objectName().isEmpty())
data += QString(" \"%1\"").arg(entity->objectName());
return data;
}
QVariant Qt3DEntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
return {};
}
int Qt3DEntityTreeModel::rowCount(const QModelIndex &parent) const
{
if(!parent.isValid()) return 1;
auto entity = reinterpret_cast<Qt3DCore::QEntity*>(parent.internalPointer());
if(!entity) return 0;
return entity->childNodes().count();
}
int Qt3DEntityTreeModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
Qt::ItemFlags Qt3DEntityTreeModel::flags(const QModelIndex &index) const
{
return {};
}
which works fine when displayed in a QTreeView (QtWidgets):
auto treeView = new QTreeView();
auto treeModel = new Qt3DEntityTreeModel(treeView);
treeModel->setRootEntity(rootEntity);
treeView->setModel(treeModel);
treeView->resize(640, 480);
treeView->show();
however when used in a QtQuick Controls TreeView, I cannot see the item text, and I cannot even expand the root tree item:
Item {
...
Qt3DEntityTreeModel {
id: entityTreeModel
rootEntity: root
}
TreeView {
model: entityTreeModel
TableViewColumn {
title: "Name"
width: 200
}
}
Scene3D {
id: scene3d
Entity {
id: root
...
}
}
}
I also tried to explicitly define a role in Qt3DEntityTreeModel:
qt3dentitytreemodel.h:
enum Roles {
DisplayRole = Qt::UserRole + 1
};
QHash<int, QByteArray> roleNames() const override;
qt3dentitytreemodel.cpp:
QVariant Qt3DEntityTreeModel::data(const QModelIndex &index, int role) const
{
if(!rootEntity_) return {};
if(!index.isValid()) return {};
if(role != Qt::DisplayRole && role != DisplayRole) return {};
...
}
QHash<int, QByteArray> Qt3DEntityTreeModel::roleNames() const
{
QHash<int, QByteArray> result;
result.insert(DisplayRole, QByteArrayLiteral("display"));
return result;
}
and define the respective role property in the column of the QtQuick TreeView:
TreeView {
model: entityTreeModel
TableViewColumn {
title: "Name"
role: "display"
width: 200
}
}
but still shows nothing.
Is there something else to do to be able to use a custom abstract item model with QtQuick's TreeView?
I think I found the explanation: the model is created before the Qt3D entity tree is "ready".
If I create the model in C++ after the view is loaded, then set the model as a context property, it displays correctly in the QML's TreeView:
QQuickView view;
view.resize(500, 500);
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:/main.qml"));
auto rootObject = view.rootObject();
auto rootEntity = rootObject->findChild<Qt3DCore::QEntity*>("theRootEntity");
qDebug() << "rootEntity:" << rootEntity;
auto treeModel = new Qt3DEntityTreeModel(&app);
treeModel->setRootEntity(rootEntity);
view.rootContext()->setContextProperty("theModel", treeModel);
view.show();
The fundamental problem is that my model doesn't monitor updates to the Qt3D entity tree, and it does not reflect such updates.

How to apply and view user roles?

I'm trying to create custom model, and want it to work with custom roles. But I dont really understand how to do it. Also, i want to use my model with a qt widget, not with QML View. How roles are applying to certain items?
How to setup ListView, so that could work with my custom roles?
I know that I need to create enum, and reimplement roleNames function
my model .h file
class ListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
ListModel();
virtual ~ListModel() override;
enum CustomRoles{
RoleType=Qt::UserRole+1,
ButtonRole,
CheckboxRole,
};
protected:
QList<BaseItems*> itemList;
QHash<int, QByteArray> _roles;
// int _RowCount = 0;
public:
void Add(BaseItems* item);
BaseItems* getItem(int index);
void clear();
int count() const;
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;
Q_SIGNALS:
void countChanged();
};
my model .cpp file
ListModel::ListModel() : QAbstractListModel()
{
}
ListModel::~ListModel()
{
itemList.clear();
}
void ListModel::Add(BaseItems *item)
{
beginInsertRows(QModelIndex(),itemList.count(),itemList.count());
itemList.append(item);
endInsertRows();
Q_EMIT countChanged();
}
BaseItems* ListModel::getItem(int index)
{
return itemList.at(index);
}
void ListModel::clear()
{
qDeleteAll(itemList);
itemList.clear();
}
int ListModel::count() const
{
return rowCount();
}
int ListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return itemList.count();
}
QVariant ListModel::data(const QModelIndex &index, int role) const
{
ItemButton *button = dynamic_cast<ItemButton*>(itemList.at(index.row()));
if (!index.isValid())
return QVariant();
if (index.row() >= itemList.count())
return QVariant();
switch (role)
{
case Qt::DisplayRole:{
return QVariant::fromValue(button->Text);}
case ButtonRole:{
return QVariant::fromValue(button->Text);}
}
return QVariant();
}
QHash<int, QByteArray> ListModel::roleNames() const {
QHash<int, QByteArray> role;
role[RoleType] = "first";
role[ButtonRole] = "last";
return role;
}
Instead of "first" and "last" better you name the roles meaningfully:
QHash<int, QByteArray> ListModel::roleNames() const {
QHash<int, QByteArray> role;
role[RoleType] = "roleType";
role[ButtonRole] = "buttonRole";
return role;
}
So these quoted names will be used. If you want to display the data from this model in QML you can do something like this:
ListView {
width: 100
height: 500
model: listModel
delegate: Text {
text: model.roleType + model.buttonRole
}
}
listModel object can be initialized in C++ and can be passed to QML using
view->rootContext()->setContextProperty("listModel", listModel);
or you can make an instance of the ListModel in QML, but in the cpp file you will have to register your ListModel type
qmlRegisterType<ListModel>("ListModel", 1, 0, "ListModel");
then in the qml file:
import ListModel 1.0
finally creating an instance of the model by
ListModel {
id: listModel
}

change c++ QAbstractListModel values from Qml

this is my code
devicemodel.h
class Device
{
public:
Device(const int &nodeId ,const QString &type, const int &lampVoltage);
//![0]
QString type() const;
int lampVoltage() const;
int nodeId() const;
public:
QString m_type;
int m_lampVoltage;
int m_nodeId;
//![1]
};
class DeviceModel : public QAbstractListModel
{
Q_OBJECT
public:
enum DeviceRoles {
NodeIdRole = Qt::UserRole + 1,
TypeRole ,
LampVoltageRole
};
DeviceModel(QObject *parent = 0);
//![1]
void addDevice(const Device &Device);
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<Device> m_Devices;
//![2]
public slots :
void callFromQml(int index);
};
devicemodel.cpp
Device::Device(const int &nodeId ,const QString &type, const int &lampVoltage)
: m_nodeId(nodeId) , m_type(type), m_lampVoltage(lampVoltage)
{
}
QString Device::type() const
{
return m_type;
}
int Device::nodeId() const
{
return m_nodeId;
}
int Device::lampVoltage() const
{
return m_lampVoltage;
}
DeviceModel::DeviceModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void DeviceModel::callFromQml(int index){
Device k= m_Devices.at(index);
qDebug() << k.type() << k.lampVoltage();
k.m_lampVoltage = 5;
emit dataChanged(createIndex(index,0), createIndex(index,0), {0,1,2} );
}
void DeviceModel::addDevice(const Device &Device)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_Devices << Device;
endInsertRows();
}
int DeviceModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_Devices.count();
}
QVariant DeviceModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_Devices.count())
return QVariant();
const Device &Device = m_Devices[index.row()];
if (role == NodeIdRole)
return Device.nodeId();
else if (role == TypeRole)
return Device.type();
else if (role == LampVoltageRole)
return Device.lampVoltage();
return QVariant();
}
//![0]
QHash<int, QByteArray> DeviceModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NodeIdRole] = "nodeId";
roles[TypeRole] = "type";
roles[LampVoltageRole] = "lampVoltage";
return roles;
}
main.qml
ListView {
width: 200; height: 250
spacing: 10
model: myModel
delegate:
Text { text: "Animal: " + type + ", " + lampVoltage
MouseArea {
anchors.fill: parent
onClicked: {
console.log("egwegweg");
myModel.callFromQml(index);
//lampVoltage = 10;
// myModel.setData(index , 10 , 2);
}
}
}
}
main.cpp
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
DeviceModel model;
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
model.addDevice(Device(1, "Medium" , 200));
return app.exec();
i want modify my item values from Qml
i wrote a slot named callFromQml and pass index of item from qml to c++ code and want update the values from it
but can not do it
i don not know emit signal works or not and do not know pass index to it correctly or not and don not
Thanks for #MarkCh for pointing out, that you have not reimplemented the setData()-method.
The signature is:
bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
It returns true if some data was set successfully, and false if not.
Further, uppon success, before returning, it is necessary to emit dataChanged(...) so any views will be aware of the change.
Let's do it:
Add the appropriate declaration to the header file
Add the implementation to the .cpp-file. This might look like this:
bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= m_Devices.count())
return false;
const Device &Device = m_Devices[index.row()];
if (role == NodeIdRole) {
Device.m_nodeId = value.toInt();
emit dataChanged(index, index);
return true;
}
else if (role == TypeRole) {
Device.m_type = value.toString();
emit dataChanged(index, index);
return true;
}
else if (role == LampVoltageRole) {
Device.m_lampVoltage = value.toInt();
emit dataChanged(index, index);
return true;
}
return false;
}
Make DeviceModel a friend of Device or change the access of the private fields to usage of getters. As you like.
Now you should be able to set the roles in the delegate as if they were properties... Like:
onClicked: model.nodeId = 100;
The code above is not tested, but should include the most relevant details.

How to access Items stored in a QAbstractListmodel in QML(by delegates) otherwise than using item roles?

I just want to display elements from list using QML, but not using item roles.
For ex. I want to call a method getName() that returns the name of the item to be displayed.
Is it possible? I didn't find nothing clear reffering to this.
You can use one special role to return the whole item as you can see below:
template<typename T>
class List : public QAbstractListModel
{
public:
explicit List(const QString &itemRoleName, QObject *parent = 0)
: QAbstractListModel(parent)
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole] = QByteArray(itemRoleName.toAscii());
setRoleNames(roles);
}
void insert(int where, T *item) {
Q_ASSERT(item);
if (!item) return;
// This is very important to prevent items to be garbage collected in JS!!!
QDeclarativeEngine::setObjectOwnership(item, QDeclarativeEngine::CppOwnership);
item->setParent(this);
beginInsertRows(QModelIndex(), where, where);
items_.insert(where, item);
endInsertRows();
}
public: // QAbstractItemModel
int rowCount(const QModelIndex &parent = QModelIndex()) const {
Q_UNUSED(parent);
return items_.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (index.row() < 0 || index.row() >= items_.count()) {
return QVariant();
}
if (Qt::UserRole == role) {
QObject *item = items_[index.row()];
return QVariant::fromValue(item);
}
return QVariant();
}
protected:
QList<T*> items_;
};
Do not forget to use QDeclarativeEngine::setObjectOwnership in all your insert methods. Otherwise all your objects returned from data method will be garbage collected on QML side.

Resources