My goal is to implement a sort of simulator, with high rate data updates.
The application is composed from the following parts:
A data model: it stores the data and it is used by a TableView as a model
A simulator: a thread which updates the whole data model every 250ms
A Qml view: contains a TableView to show the data
As soon as I start the application, I can see the data changing in the TableView, but there is a wierd crash if I try to scroll the TableView using the mouse wheel (keep scrolling for a while).
The only log I get is the following:
ASSERT failure in QList::at: "index out of range", file C:\work\build\qt5_workdir\w\s\qtbase\include/QtCore/../../src/corelib/tools/qlist.h, line 510
The more interesting thing is that I get this crash only in a Windows environment, while in a CentOs machine I do not get any error.
I tried here to extract the main part of my project and to generalize it as much as possibile. Find below the code, or if you prefer you can download the full project from this link
mydata.h
#ifndef MYDATA_H
#define MYDATA_H
#include <QObject>
class MyData : public QObject
{
Q_OBJECT
Q_PROPERTY(int first READ first WRITE setFirst NOTIFY firstChanged)
Q_PROPERTY(int second READ second WRITE setSecond NOTIFY secondChanged)
Q_PROPERTY(int third READ third WRITE setThird NOTIFY thirdChanged)
public:
explicit MyData(int first, int second, int third, QObject* parent=0);
int first()const {return m_first;}
int second()const {return m_second;}
int third()const {return m_third;}
void setFirst(int v){m_first=v;}
void setSecond(int v){m_second=v;}
void setThird(int v){m_third=v;}
signals:
void firstChanged();
void secondChanged();
void thirdChanged();
private:
int m_first;
int m_second;
int m_third;
};
#endif // MYDATA_H
mydata.cpp
#include "mydata.h"
MyData::MyData(int first, int second, int third, QObject* parent) : QObject(parent)
{
m_first=first;
m_second=second;
m_third=third;
}
datamodel.h
#ifndef DATAMODEL_H
#define DATAMODEL_H
#include <QAbstractListModel>
#include <QMutex>
#include "mydata.h"
class DataModel: public QAbstractListModel
{
Q_OBJECT
public:
enum DataModelRoles {
FirstRole = Qt::UserRole + 1,
SecondRole,
ThirdRole
};
//*****************************************/
//Singleton implementation:
static DataModel& getInstance()
{
static DataModel instance; // Guaranteed to be destroyed.
// Instantiated on first use.
return instance;
}
DataModel(DataModel const&) = delete;
void operator=(DataModel const&) = delete;
//*****************************************/
QList<MyData*>& getData(){return m_data;}
void addData(MyData* track);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = FirstRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QMutex m_mutex;
QList<MyData*> m_data;
DataModel(QObject* parent=0);
};
#endif // DATAMODEL_H
datamodel.cpp
#include "DataModel.h"
#include "QDebug"
DataModel::DataModel(QObject* parent): QAbstractListModel(parent)
{
}
void DataModel::addData(MyData *track)
{
beginInsertRows(QModelIndex(),rowCount(),rowCount());
m_data<<track;
endInsertRows();
}
int DataModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_data.size();
}
QVariant DataModel::data(const QModelIndex &index, int role) const
{
MyData* data=m_data[index.row()];
switch (role) {
case FirstRole:
return data->first();
case SecondRole:
return data->second();
case ThirdRole:
return data->third();
default:
return QVariant();
}
}
QHash<int, QByteArray> DataModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[FirstRole] = "First";
roles[SecondRole] = "Second";
roles[ThirdRole] = "Third";
return roles;
}
simulator.h
#ifndef SIMULATOR_H
#define SIMULATOR_H
#include <QThread>
class Simulator: public QThread
{
Q_OBJECT
public:
Simulator(QObject* parent=0);
void run() Q_DECL_OVERRIDE;
private:
void createNewData();
void updateExistingData();
int randInt(int from, int to);
};
#endif // SIMULATOR_H
simulator.cpp
#include "simulator.h"
#include <math.h>
#include <mydata.h>
#include <datamodel.h>
Simulator::Simulator(QObject* parent) : QThread(parent)
{
createNewData();
}
void Simulator::run()
{
long updateRate=250;
while(true)
{
updateExistingData();
msleep(updateRate);
}
}
void Simulator::createNewData()
{
int numOfData=10000;
for(int i=0;i<numOfData;i++)
{
int first=i;
int second=randInt(0,1000);
int third=randInt(0,1000);
MyData* data=new MyData(first,second,third);
DataModel::getInstance().addData(data);
}
}
void Simulator::updateExistingData()
{
QList<MyData*> list=DataModel::getInstance().getData();
for(int i=0;i<list.size();i++)
{
MyData* curr=list.at(i);
curr->setSecond(curr->second()+1);
curr->setThird(curr->third()+2);
QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
emit DataModel::getInstance().dataChanged(index,index);
}
}
int Simulator::randInt(int from, int to)
{
// Random number between from and to
return qrand() % ((to + 1) - from) + from;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "simulator.h"
#include "datamodel.h"
#include <QQmlContext>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
DataModel& model=DataModel::getInstance();
Simulator* s=new Simulator();
s->start();
QQmlApplicationEngine engine;
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.4
Window {
visible: true
width: 800
height: 600
TableView {
id: tableView
width: parent.width
height: parent.height
frameVisible: true
model: myModel
sortIndicatorVisible: true
property string fontName: "Arial"
TableViewColumn {
id: firstColumn
title: "First"
role: "First"
movable: false
resizable: false
width: tableView.viewport.width/3
delegate: Text{
font.family: tableView.fontName
text: styleData.value
horizontalAlignment: TextInput.AlignHCenter
verticalAlignment: TextInput.AlignVCenter
}
}
TableViewColumn {
id: secondColumn
title: "Second"
role: "Second"
movable: false
resizable: false
width: tableView.viewport.width/3
delegate: Text{
font.family: tableView.fontName
text: styleData.value
horizontalAlignment: TextInput.AlignHCenter
verticalAlignment: TextInput.AlignVCenter
}
}
TableViewColumn {
id: thirdColumn
title: "Third"
role: "Third"
movable: false
resizable: false
width: tableView.viewport.width/3
delegate: Text{
font.family: tableView.fontName
text: styleData.value
horizontalAlignment: TextInput.AlignHCenter
verticalAlignment: TextInput.AlignVCenter
}
}
}
}
I am glad to share with you the solution to my own answers, hoping it could help someone who is getting the same error.
The key point of the problem is here:
void Simulator::updateExistingData()
{
QList<MyData*> list=DataModel::getInstance().getData();
for(int i=0;i<list.size();i++)
{
MyData* curr=list.at(i);
curr->setSecond(curr->second()+1);
curr->setThird(curr->third()+2);
QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
emit DataModel::getInstance().dataChanged(index,index); //ERROR!
}
}
In fact I am emitting a signal on the DataModel in a thread that is not the Gui thread: this will fall into a concurrent access issue, between the gui thread (which is accessing the data to fill the TableView) and the updater thread (which is accessing the data to update the values).
The solution is to emit the dataChanged signal on the gui thread, and we can do that by using the Qt Signal/Slot mechanism.
Therefore:
In datamodel.h:
public slots:
void updateGui(int rowIndex);
In datamodel.cpp:
void DataModel::updateGui(int rowIndex)
{
QModelIndex qIndex=index(rowIndex,0, QModelIndex());
dataChanged(qIndex,qIndex);
}
In simulator.h:
signals:
void dataUpdated(int row);
In simulator.cpp:
Simulator::Simulator(QObject* parent) : QThread(parent)
{
createNewData();
connect(this,SIGNAL(dataUpdated(int)), &DataModel::getInstance(), SLOT(updateGui(int)));
}
...
void Simulator::updateExistingData()
{
QList<MyData*> list=DataModel::getInstance().getData();
for(int i=0;i<list.size();i++)
{
MyData* curr=list.at(i);
curr->setSecond(curr->second()+1);
curr->setThird(curr->third()+2);
QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
emit dataUpdated(i);
}
}
Using the Signal/Slot approach, we are sure that the request will be handled in the receiver class by the thread who have created that class (the Gui thread), therefore the following dataChanged signal will be emitted by the proper thread.
Related
So I have a custom QAbstractItemModel that overrides the following functions:
QVariant data(..) const;
QVariant headerData(..) const;
QModelIndex index(..) const;
QModelIndex parent(..) const;
int rowCount(..) const;
int columnCount(..) const;
virtual bool removeRows(..);
Is it necessary to override all of these in a custom QSortFilterProxyModel that uses my model as a source?
The source of my confusion is this excerpt from the manual:
Since QAbstractProxyModel and its subclasses are derived from
QAbstractItemModel, much of the same advice about subclassing normal
models also applies to proxy models. In addition, it is worth noting
that many of the default implementations of functions in this class
are written so that they call the equivalent functions in the relevant
source model. This simple proxying mechanism may need to be overridden
for source models with more complex behavior; for example, if the
source model provides a custom hasChildren() implementation, you
should also provide one in the proxy model.
https://doc.qt.io/archives/qt-4.8/qsortfilterproxymodel.html#subclassing
No, you don't need to override these functions.
Simple example QSortFilterProxyModel usage below:
Model.hpp
#pragma once
#include <QAbstractListModel>
class Model : public QAbstractListModel {
Q_OBJECT
public:
enum Roles {
Name = Qt::UserRole + 1
};
Model(QObject* parent = nullptr);
virtual ~Model();
// QAbstractItemModel interface
int rowCount(const QModelIndex &parent) const noexcept override;
QVariant data(const QModelIndex &index, int role) const noexcept override;
QHash<int, QByteArray> roleNames() const noexcept override;
private:
QStringList list_;
};
Model.cpp
#include "Model.hpp"
Model::Model(QObject *parent) : QAbstractListModel{parent}
{
list_ << "Adam" << "John" << "Alice" << "Kate";
}
Model::~Model()
{
}
int Model::rowCount(const QModelIndex &parent) const noexcept
{
return list_.size();
}
QVariant Model::data(const QModelIndex &index, int role) const noexcept
{
if(!index.isValid() || role < Name)
return QVariant{};
auto name = list_[index.row()];
if(role == Name)
return name;
return QVariant{};
}
QHash<int, QByteArray> Model::roleNames() const noexcept
{
return QHash<int, QByteArray>{{Name, "name"}};
}
FilterModel.hpp
#pragma once
#include <QSortFilterProxyModel>
class FilterModel : public QSortFilterProxyModel {
Q_OBJECT
public:
FilterModel(QObject* parent = nullptr);
virtual ~FilterModel();
Q_INVOKABLE void setFilterString(const QString& filter) noexcept;
};
FilterModel.cpp
#include "FilterModel.hpp"
#include "Model.hpp"
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel{parent}
{
}
FilterModel::~FilterModel()
{
}
void FilterModel::setFilterString(const QString &filter) noexcept
{
setFilterCaseSensitivity(Qt::CaseInsensitive);
setFilterFixedString(filter);
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "Model.hpp"
#include "FilterModel.hpp"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Model model;
FilterModel filterModel;
filterModel.setSourceModel(&model);
filterModel.setFilterRole(Model::Name);
QQmlApplicationEngine engine;
QQmlContext* ctx = engine.rootContext();
ctx->setContextProperty("filterModel", &filterModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.11
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 200
height: 300
ColumnLayout {
anchors.fill: parent
TextField {
id: textField
Layout.fillWidth: true
placeholderText: "Search..."
onTextChanged: { filterModel.setFilterString(textField.text) }
}
ListView {
id: view
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
model: filterModel
delegate: Text {
width: parent.width
text: name
font.pointSize: 14
font.bold: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
}
}
Notice if you have a custom class as an item of model, you have to override
bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
for your filter.
I have followed this tutorial : https://resources.qt.io/resources-by-content-type-videos-demos-tutorials/using-c-models-in-qml-tutorial
This was usefull to create my c++ class to manage my LightModel
Now I want to re-use this tutorial where I have a model inside 2 other repeaters in cascade. The issue is that my qml code instantiate LightModel objects inside repeaters so I don't have access to these instances from main.cpp
I used qmlRegisterType to give qml the ability to create several objects from my LightModel class.
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
qmlRegisterType<Light>("Light",1,0,"Light");
qmlRegisterType<LightModel>("Light", 1,0,"LightModel");
qmlRegisterUncreatableType<Light>("Light", 1, 0, "LightList", QStringLiteral("LightList should not be created in QML"));
QQmlApplicationEngine engine;
MainWindow w;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
w.show();
return app.exec();
}
light.h
#ifndef LIGHT_H
#define LIGHT_H
#include <QObject>
#include <QVector>
struct LightItem{
QString description;
int value;
int bornInf;
int bornSup;
int decimals; //1, 10
bool enabled;
};
class Light : public QObject
{
Q_OBJECT
public:
explicit Light(QObject *parent = nullptr);
QVector<LightItem> items() const;
bool setItemAt(int index, const LightItem &item);
signals:
void preItemAppended();
void postItemAppended();
void preItemRemoved(int index);
void postItemRemoved();
private:
QVector<LightItem> mItems;
};
#endif // LIGHT_H
lightmodel.h
#ifndef LIGHTMODEL_H
#define LIGHTMODEL_H
#include <QAbstractListModel>
class Light;
class LightModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(Light *list READ list WRITE setList)
Q_PROPERTY(int ledMode READ getLedMode WRITE setLedMode)
public:
explicit LightModel(QObject *parent = nullptr);
enum{
DescriptionRole = Qt::UserRole,
ValueRole,
BornInfRole,
BornSupRole,
Decimals,
EnableRole
};
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual QHash<int, QByteArray> roleNames() const override;
Light *list() const;
void setList(Light *list);
int getLedMode() const;
void setLedMode(int value);
private:
Light *mList;
int ledMode;
};
#endif // LIGHTMODEL_H
main.qml
StackLayout {
id: lampChose
currentIndex: gridLamps.indexImage
Repeater {
id: repeater1
model: 24
StackLayout {
id: lampStack
currentIndex: tabBarNm.currentIndex
Repeater {
id: repeater2
model: 7
Column {
id: columnSpinBoxtConfLeds
Repeater {
id: repeaterParametersSpinBoxesLEDs
model: LightModel {
id: lightModel
list: light }
SpinBox {
id: spinBoxTest
visible: true
editable: true
value: model.value
from: model.bornInf
to: model.bornSup
stepSize: 1
onValueChanged: {
model.value = value
}
}
}
}
}
}
}
My LightModel class is implemented like ToDoModel in the tutorial exemple : https://github.com/mitchcurtis/todo-list-tutorial/tree/chapter-11
So the question is : how can I access from C++ the content of the LightModel objects because the 24x7 instances are created by .qml file.
Thanks a lot for your time
I've found the solution to my problem : stop using repeaters with StackLayout.
My editable content is stored inside an array, set as context property inside my main.cpp and the view get the value corresponding to the current index
light.h
#ifndef LIGHT_H
#define LIGHT_H
#include <QObject>
class LightV2 : public QObject
{
Q_OBJECT
public:
explicit LightV2(QObject *parent = nullptr);
public slots:
Q_INVOKABLE int getArrayValue(int indexLamp, int indexLight, int indexField);
Q_INVOKABLE int getBornInf(int indexLamp, int indexLight, int indexField);
Q_INVOKABLE int getBornSup(int indexLamp, int indexLight, int indexField);
Q_INVOKABLE int getDecimal(int indexLamp, int indexLight, int indexField);
Q_INVOKABLE void setArrayValue(int indexLamp, int indexLight, int indexField, int value);
private:
QString mDescription;
int mValue;
int mArrayValue[24][7][6];
int mBornInf[24][7][6];
int mBornSup[24][7][6];
int mDecimal[24][7][6];
};
#endif // LIGHTV2_H
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
qmlRegisterType<GeneralConf>("Light", 1,0, "GeneralConf");
QQmlApplicationEngine engine;
LightV2 lights;
MainWindow w;
engine.rootContext()->setContextProperty("lights", &lights);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
w.show();
return app.exec();
}
finally : solution in main.qml
Column {
id: columnSpinBoxtConfLeds
x:110
y:0
width:200
spacing: 10
Repeater {
id: repeaterParametersSpinBoxesLEDs
model: 6
SpinBox {
id: spinBoxTest
property int decimals: lights.getDecimal(gridLamps.indexImage,tabBarNm.currentIndex, model.index);
property int decimalPresent:{
if (decimals == 10){
1
}
else{
0
}
}
width : 150
height: 25
focusPolicy: Qt.TabFocus
wheelEnabled: false
visible: true
editable: true
enabled: {
console.log("spinbox index:",model.index)
if (model.index == 2 || model.index == 3){
if(comboBoxMode.currentIndex >=1){
1
}
else{
0
}
}
else if (model.index >= 4){
if(comboBoxMode.currentIndex >=2){
1
}
else{
0
}
}
else{
1
}
}
value: lights.getArrayValue(gridLamps.indexImage,tabBarNm.currentIndex, model.index)
from : lights.getBornInf(gridLamps.indexImage,tabBarNm.currentIndex, model.index)
to : lights.getBornSup(gridLamps.indexImage,tabBarNm.currentIndex, model.index)
stepSize: 1
textFromValue: function(value, locale){
return Number(value / decimals).toLocaleString(locale, 'f',decimalPresent)
}
valueFromText: function(text,locale){
return Number.fromLocaleString(locale, text) * decimals
}
onValueChanged: {
lights.setArrayValue(gridLamps.indexImage,tabBarNm.currentIndex, model.index, value);
console.log(lights.getArrayValue(gridLamps.indexImage,tabBarNm.currentIndex, model.index));
}
}
to conclude : this is how I managed to resolve my issue :
lights.getArrayValue(gridLamps.indexImage,tabBarNm.currentIndex, model.index)
and
lights.setArrayValue(gridLamps.indexImage,tabBarNm.currentIndex, model.index, value);
I am trying to expose QList with custom objects (Sample) into QML. Whenever I store those custom objects (they inherits form QObject) into QList<QObject *>, they show up, but without info, but when I try expose them as a QList<Sample *>, they don't.
sample.h
class Sample : public QObject
{
Q_OBJECT
Q_PROPERTY(QString getVar READ getVar WRITE setVar NOTIFY varChanged)
public:
explicit Sample();
//! Returns var
QString getVar() const { return var; }
//! Sets var
void setVar(const QString &a);
signals:
varChanged();
protected:
QString var;
};
Class containing list looks like this
samplecontrol.h
class SampleManager : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<Sample *> getSampleList READ getSampleList NOTIFY sampleListChanged)
public:
SampleManager(const QString &path);
//! Returns sample list
QList<Sample *> getSampleList() const { return sampleList_; }
signals:
sampleListChanged();
protected:
QList<Sample *> sampleList_;
};
I am setting context with
view_->rootContext()->setContextProperty("slabGridModel", QVariant::fromValue(samplecontrol.getSampleList()));
As I said, when i tired
QList<QObject *> datalist;
datalist.append(sampleManager.getSampleList().first());
datalist.append(sampleManager.getSampleList().last());
it worked. How can I make it work with QList<Sample *>?
Thanks for your help!
You can pass a list of QObjects, but the problem is that it will not notify the view if more elements are added, if you want the view to be notified you must use a model that inherits from QAbstractItemModel. On the other hand how are you doing the datamodel as qproperty it is better to expose the SampleManager:
samplemodel.h
#ifndef SAMPLEMODEL_H
#define SAMPLEMODEL_H
#include "sample.h"
#include <QAbstractListModel>
class SampleModel : public QAbstractListModel
{
Q_OBJECT
public:
using QAbstractListModel::QAbstractListModel;
~SampleModel(){
qDeleteAll(mSamples);
mSamples.clear();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override{
if (parent.isValid())
return 0;
return mSamples.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override{
if (!index.isValid())
return QVariant();
if(role == Qt::UserRole){
Sample *sample = mSamples[index.row()];
return QVariant::fromValue(sample);
}
return QVariant();
}
void appendSample(Sample * sample)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
mSamples << sample;
endInsertRows();
}
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[Qt::UserRole] = "sample";
return roles;
}
private:
QList<Sample *> mSamples;
};
#endif // SAMPLEMODEL_H
samplemanager.h
#ifndef SAMPLEMANAGER_H
#define SAMPLEMANAGER_H
#include "samplemodel.h"
#include <QObject>
class SampleManager : public QObject
{
Q_OBJECT
Q_PROPERTY(SampleModel* model READ model WRITE setModel NOTIFY modelChanged)
public:
using QObject::QObject;
SampleModel *model() const{
return mModel.get();
}
void setModel(SampleModel *model){
if(mModel.get() == model)
return;
mModel.reset(model);
}
signals:
void modelChanged();
private:
QScopedPointer<SampleModel> mModel;
};
#endif // SAMPLEMANAGER_H
main.cpp
#include "samplemanager.h"
#include "samplemodel.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTime>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
SampleManager manager;
manager.setModel(new SampleModel);
// test
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&manager](){
manager.model()->appendSample(new Sample(QTime::currentTime().toString()));
});
timer.start(1000);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("manager", &manager);
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")
GridView {
anchors.fill: parent
model: manager.model
delegate:
Rectangle {
width: 100
height: 100
color: "darkgray"
Text {
text: sample.getVar
anchors.centerIn: parent
}
}
}
}
The complete example can be found in the following link.
I am trying to make the model QAbstractTableModel in cpp and connect to qml.
This code works well.
MyModel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
explicit MyModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Animal> m_animals;
};
#endif // MYMODEL_H
MyModel.cpp
#include "MyModel.h"
#include <QDebug>
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
qDebug() << __FUNCTION__;
addAnimal(Animal("Wolf", "Medium"));
addAnimal(Animal("Polar bear", "Large"));
addAnimal(Animal("Quoll", "Small"));
}
int MyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_animals.size();
}
int MyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 2;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
qDebug() << __FUNCTION__ << index.row() << index.column() << role;
if (!index.isValid())
return QVariant();
const Animal &animal = m_animals[index.row()];
switch (role) {
case TypeRole:
return animal.type();
case SizeRole:
return animal.size();
default:
break;
}
return QVariant();
}
void MyModel::addAnimal(const Animal &animal)
{
qDebug() << __FUNCTION__;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
qDebug() << __FUNCTION__;
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MyModel model;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main_test.qml
import QtQuick 2.0
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: main_view
width: 250
height: 600
visible: true
ListView {
id: list_view
width: 200; height: 250
model: myModel
delegate: Text { text: "Animal Test: " + type + ", " + size }
}
TableView {
id: table_view
objectName: "tableView"
width: 250; height: 250
anchors.top: list_view.bottom
model: myModel
TableViewColumn {
id: type_col
role: "type"
title: "Type"
width: 100
}
TableViewColumn {
id: size_col
role: "size"
title: "Size"
width: 100
}
}
}
It looks like this
But, if I change the main.cpp a little bit, the list view dispaly as normal, but not the table view.
main.cpp
#include "MainView.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MainView mainView;
return app.exec();
}
MainView.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
};
#endif // MAINVIEW_H
MainView.cpp
#include "MainView.h"
#include "MyModel.h"
#include <QQmlContext>
MainView::MainView(QObject *parent)
: QObject(parent)
{
initializeView();
}
void MainView::initializeView()
{
MyModel model;
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
It looks like this.
I really don't understand why this happens. What the difference between ListView and TableView in the second case? And how to fix it, i.e. make data display in the second case? Thank in advance.
The problem is in initializeView, model is a local variable in that scope, and every local variable is deleted from the memory when the function finishes executing. In the first case model will be eliminated when the application is closed, in the second case it will be eliminated when initializeView ends that is when the window is displayed, there are 2 possible solutions:
Create a pointer and to manage the memory we pass to MainView as a parent so that he removes it from memory (this last is a feature of the QObjects):
void MainView::initializeView()
{
MyModel *model = new MyModel(this);
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
Make the model member of the class.
*.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
MyModel model{this};
};
#endif // MAINVIEW_H
*.cpp
...
void MainView::initializeView()
{
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
To understand the behavior I have added more points of impression:
MyModel::~MyModel()
{
qDebug()<<"destructor";
}
TableView {
...
Component.onCompleted: {
console.log("completed table")
if(!timer.running)
timer.running = true
}
}
ListView {
...
Component.onCompleted: {
console.log("completed list")
if(!timer.running)
timer.running = true
}
}
Timer {
id: timer
interval: 0;
onTriggered: console.log(myModel, table_view.model, list_view.model)
}
And I get the following:
MyModel
addAnimal
addAnimal
addAnimal
roleNames
data 0 0 257
data 0 0 258
data 1 0 257
data 1 0 258
data 2 0 257
data 2 0 258
roleNames
qml: completed list
data 0 0 257
data 0 0 258
qml: completed table
destructor
qml: null null null
We note that ListView manages to load the data, whereas TableView in the middle of the load is called the destructor.
Possible explanation: I think that the ListView stores the data making a copy of them and only updates when the model notifies, should also be notified when the model is deleted to clean the data, it seems that this is a bug. On the other hand, TableView, being at the moment of loading and deleting the model, is null, so it is notified and cleans the data.
Doing another test:
void MainView::initializeView()
{
MyModel *model = new MyModel;
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QTimer::singleShot(1000, model, &MyModel::deleteLater);
}
It is observed that both load the data correctly, and after a second the model is destroyed, but the one that is only notified is the TableView since it is the only one that cleans the data shown, and ListView does not.
my conclusion that it is a ListView bug.
I tried to add to a ListView in QML of N Items a way to add and delete a new Item at a given index.
I did the following example, but the problem is that when I move some Items, when I try to insert a new one, the position might be incorrect and I have no clue why. When I check my DataList in my cpp model, positions are correct, however, new or deleted items won't be inserted/deleted at the right position.
It seems that the error occurs when I insert a new Item, then I move it , and then I try to delete this Item or insert an Item next to this New Item.
Here is a simple example (you can run it if you need). I called my Items Data : Blocks
#include "mainwindow.h"
#include <QApplication>
#include <QtQml>
#include <QQuickView>
#include <QQuickWidget>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
main.cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "model.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
void addItem(int index);
~MainWindow();
private slots:
private:
QList<QObject*> dataList;
Ui::MainWindow *ui;
BlockModel model;
int cpt = 0;
};
#endif // MAINWINDOW_H
mainwindow.h
#include <QtQml>
#include <QQuickView>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QQuickWidget"
#include <QStringList>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
int nbItems = 5;
for(; cpt < nbItems; cpt ++) {
Block a = Block(QString("Item ")+QString::number(cpt));
model.addBlock(a);
}
ui->setupUi(this);
QQuickWidget *view = new QQuickWidget;
QQmlContext *ctxt = view->rootContext();
ctxt->setContextProperty("myModel", &model);
view->setSource(QUrl::fromLocalFile("main.qml"));
view->setGeometry(0, 200, 600, 400);
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
ui->dockWidget_3->setWidget(view);
}
MainWindow::~MainWindow()
{
delete ui;
}
mainwindow.cpp
#include <QAbstractListModel>
#include <QStringList>
#include <qqmlcontext.h>
#include <QDebug>
#include <QStringList>
//![0]
class Block
{
public:
Block(){
}
Block(const QString &name);
QString nameBlock() const;
void setName(QString n) {
m_name = n;
}
private:
QString m_name;
};
class BlockModel : public QAbstractListModel
{
Q_OBJECT
public:
Block* getBlock(QString name);
Q_INVOKABLE void moveBlock(int from,int to);
Q_INVOKABLE void insertBlock(int index);
Q_INVOKABLE void deleteBlock(int index);
enum BlockRoles {
nameRole = Qt::UserRole + 1,
};
BlockModel(QObject *parent = 0);
void setContext(QQmlContext *ctx) {
m_ctx = ctx;
}
void setName(const QString &name);
void addBlock(const Block &Block);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
private:
QList<Block> m_blocks;
QQmlContext* m_ctx;
int cpt = 0;
};
mode.h
#include "model.h"
#include "qDebug"
Block::Block(const QString &name)
: m_name(name)
{
}
QString Block::nameBlock() const
{
return m_name;
}
BlockModel::BlockModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void BlockModel::addBlock(const Block &Block)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_blocks << Block;
endInsertRows();
}
int BlockModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_blocks.count();
}
void BlockModel::moveBlock(int from, int to) {
m_blocks.move(from,to);
}
void BlockModel::insertBlock(int index) {
Block b =(Block(QString("New Item ")+QString::number(cpt)));
beginInsertRows(QModelIndex(),index+1,index+1);
m_blocks.insert(index+1,b);
endInsertRows();
cpt++;
}
void BlockModel::deleteBlock(int index) {
beginRemoveRows(QModelIndex(),index,index);
m_blocks.removeAt(index);
endRemoveRows();
}
QVariant BlockModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_blocks.count())
return QVariant();
const Block &Block = m_blocks[index.row()];
if (role == nameRole)
return Block.nameBlock();
return QVariant();
}
//![0]
QHash<int, QByteArray> BlockModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[nameRole] = "nameBlock";
return roles;
}
model.cpp
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls.Styles 1.4
Rectangle {
id : rootRectangle
visible: true
ScrollView {
anchors.fill:parent
ListView{
id: root
width: parent.width; height: parent.height
property int visualIndex: -1
displaced: Transition {
NumberAnimation { properties: "y"; easing.type: Easing.OutQuad }
}
model: DelegateModel {
id: visualModel
model: myModel
delegate: Component {
MouseArea {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
cursorShape: Qt.PointingHandCursor
width: root.width; height: 100
drag.target: icon
drag.axis: Drag.YAxis
Behavior on height {
PropertyAnimation { duration: 100 }
}
Rectangle {
anchors.top: delegateRoot.top
anchors.left: delegateRoot.left
id: icon
objectName: nameBlock
width: root.width-5; height: 100
color: "skyblue"
radius: 3
Text {
objectName: "rect"
id: title
anchors.fill: parent
anchors.margins: 10
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: nameBlock
}
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: 36
Drag.hotSpot.y: 36
Button {
id : buttonAdd
text: "Add Block"
anchors{
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.insertBlock(visualIndex)
}
}
Button {
id : buttonDelete
text: "Delete Block"
anchors{
right: buttonAdd.left
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.deleteBlock(visualIndex)
}
}
states: [
State {
when: icon.Drag.active
ParentChange {
target: icon
parent: root
}
AnchorChanges {
target: icon;
anchors.horizontalCenter: undefined;
anchors.verticalCenter: undefined
}
}
]
transitions: Transition {
// Make the state changes smooth
ParallelAnimation {
ColorAnimation { property: "color"; duration: 500 }
NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width,font.pixelSize,font.bold,visible" }
}
}
}
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
myModel.moveBlock(drag.source.visualIndex,delegateRoot.visualInde)
}
}
}
}
}
}
}
}
main.qml
Do you have any idea of what I am doing wrong ?
Thanks a lot and have a good day !
There are two bugs when moving items. In DropArea.onEntered, if you print out both drag.source.visualIndex and delegateRoot.visualIndex before and after visualModel.items.move, you'll see that values are modified after moving. That means you are moving wrong rows when calling myModel.moveBlock. To fix the problem, save the value before moving items:
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
var from = drag.source.visualIndex;
var to = delegateRoot.visualIndex;
myModel.moveBlock(from, to);
}
}
When moving items in C++ model, QAbstractItemModel::beginMoveRows should be called just like insert/remove items. Otherwise the QML DelegateModel cannot correctly display your model. Remember that when implementing BlockModel::moveBlock, the destination row for the model is different from the one for your source list m_blocks. See the last example in QAbstractItemModel::beginMoveRows documentation for detail.
void BlockModel::moveBlock(int from, int to) {
if (from == to)
return;
auto modelFrom = from;
auto modelTo = to + (from < to ? 1 : 0);
beginMoveRows(QModelIndex(), modelFrom, modelFrom, QModelIndex(), modelTo);
m_blocks.move(from,to);
endMoveRows();
}