I have a QAbtractTableModel that is accessible to a QML file with chart and series and XYModelMapper. When trying to update the model, nothing shows up on the chart. I've tried different methods(even trying QStandardItemModel before) and I can't seem to get any charts to update once it's finished loading. Is there something I'm doing wrong using either the model or model mapper? I've included simplified version of the files here.
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
QVariant data(const QModelIndex& index, int role) const override;
int columnCount(const QModelIndex& parent) const override;
int rowCount(const QModelIndex& parent) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
Qt::ItemFlags flags(const QModelIndex & index) const override;
void clear();
void addData(QDateTime time, float temperature, quint32 brightness, quint8 moisture, quint16 conductivity);
private:
struct data {
QDateTime time;
float value;
};
QList<data> tableData;
};
class Obj : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractTableModel* model READ getModel NOTIFY updated)
public:
QAbstractTableModel* getModel() {return myModel;}
Q_INVOKABLE void startUpdate(); //updated emitted here eventually as well as addData called on model.
signals:
void updated();
private:
MyModel *myModel = new MyModel();
};
QVariant MyModel::data(const QModelIndex& index, int role) const
{
if(role == Qt::DisplayRole && tableData.size()-1 >= index.row()) {
if(index.column() == 0) {
return QVariant(tableData[index.row()].time.toMSecsSinceEpoch());
} else if(index.column() == 1) {
return QVariant(tableData[index.row()].value);
} else {
return QVariant();
}
} else {
return QVariant();
}
}
int MyModel::columnCount(const QModelIndex& parent) const
{
return 5;
}
int MyModel::rowCount(const QModelIndex& parent) const
{
return 24;
}
void MyModel::addData(QDateTime time, float value)
{
tableData.append(data{time, value}); //Previously tried dynamic row count with begin/end row inserted.
emit dataChanged(createIndex(0,0),createIndex(23,4));
}
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<Obj>("org.eyecreate.testmodel",1,0,"ObjModel");
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
if (engine.rootObjects().isEmpty()) {
return -1;
}
return app.exec();
}
import QtCharts 2.3 as Charts
import org.eyecreate.testmodel 1.0
ObjModel {
id: myObj
}
Charts.ChartView {
antialiasing: true
legend.visible: false
anchors.fill: parent
Charts.LineSeries {
axisX: Charts.DateTimeAxis {
format: "MMM d yyyy ha"
}
axisY: Charts.ValueAxis {
min: 0
max: 50
}
Charts.VXYModelMapper {
model: myObj.model
xColumn: 0
yColumn: 1
}
}
}
Button {
onClicked: {
myObj.startUpdate();
}
}
EDIT: I found code very similar to mine that shows the same issue here: https://bitbucket.org/johnwoltman/chartmodelmappertest/src/master/
It seems this might be a bug: https://bugreports.qt.io/browse/QTBUG-60336
I found out through trying everything that the data points are in the series correctly, just that the graph needed to be adjusted so the points were in view.
Adjusting the min/max for each axis has allowed the lines to show up.
Related
I have a ChartView in QML I'm trying to update from a data model in C++, however, I'm running into certain issues where I can't seem to figure out a way to update the LineSeries properly.
My Sample QML Code looks like:
ChartView {
id: dataChartView
animationOptions: ChartView.NoAnimation
theme: ChartView.ChartThemeDark
antialiasing: true
Layout.fillHeight: true
Layout.fillWidth: true
ValueAxis {
id: axisXdata
min: dataManager.xMin
max: dataManager.xMax
}
ValueAxis {
id: axisYdata
min: 0
max: 1
}
LineSeries {
id: dataLineSeries
name: "Angle"
axisX: axisXdata
axisY: axisYdata
}
VXYModelMapper {
id: dataModelMapper
model: dataManager.dataModel[data]
series: dataLineSeries
firstRow: 1
xColumn: 0
yColumn: 1
}
}
The underlying model is a QAbstractTableModel that looks like this:
class DataModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum{
NameRole,
ValueRole
};
explicit DataModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() 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;
void setMaxSize(int maxSize);
int maxSize() const;
Q_INVOKABLE void addData(const QPointF& point);
public:
void handleNewData(const QPointF& point);
private:
QVector<QPointF> m_data;
int m_ModelMaxSize;
signals:
void newDataAdded(const QPointF& point);
};
and the relevant addData() function simply just pushes the Point into the m_data vector
void MsclDataModel::addData(const QPointF &point)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_data.push_back(point);
endInsertRows();
}
I then have another class called DataManager which runs a function in another thread and just adds points to the data model
void DataManager::GeneratePoints()
{
setXMin(QDateTime::currentDateTime().toMSecsSinceEpoch()); // for the min and max on the horizontal axis
for(double t=0 ; ; t+=1)
{
double y = (1 + sin(t/10.0)) / 2.0;
// many data models are required
for(const auto& model : m_Models)
{
auto time = QDateTime::currentDateTime().toMSecsSinceEpoch();
setXMax(qMax(m_xMax, time));
model->handleNewData(QPointF(time, y));
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
My issue is with this method and with the number of models I have, the application slows to a crawl and crashes eventually after about a minute or so. I've tried to constrain the addData() function to only show the latest 500 points by using something like this:
void DataModel::addData(const QPointF &point)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
if(m_data.size() == 500)
{
m_data.erase(m_data.begin());
}
m_data.push_back(point);
endInsertRows();
}
However, my output looks something like:
where the data model just stops updating after the 500th point.
I've also tried using a List to pop after max size of the list has been reached but the output is still the same. What am I doing wrong here?
Instead of beginInsertRows() and endInsertRows() I've used beginResetModel() and endResetModel() which works. I assume the range for the beginInsertRows() is wrong and it doesn't send an update anymore, so it stops drawing. You would probably also need to call beginRemoveRows() I would stick with a full reset.
void DataModel::addData(const QPointF &point)
{
beginResetModel();
if (m_data.size() == 500) {
m_data.pop_front();
emit xMinChanged();
}
m_data.push_back(point);
emit xMaxChanged();
endResetModel();
}
Edit: This will also work.
void DataModel::addData(const QPointF &point)
{
if (m_data.size() == 500) {
beginRemoveRows(QModelIndex(), 0, 0);
m_data.pop_front();
endRemoveRows();
emit xMinChanged();
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_data.push_back(point);
endInsertRows();
emit xMaxChanged();
}
I'm building GUI to control my ADC, connected to the STM32 board. Communications are handled by UART. Everything works fine. The problem is to properly handling my custom channel structure as a model object to be able to present data in different ways depending on the delegate object.
I have tried a few different solutions, none of them works. The case looks trivial however is a little different than presented examples on the forum or in tutorials on the web because I'm trying to update the data model from c++ side, not from qml. That forces me to invoke setData() override method to be invoked on c++ side and propagate dataChange() signal into qml. Let's present some code.
That's how the app presents the data models after initialization. This data are provided in SensorList constructor. No matter what I tried, I can not change those values on qml side. Even when I confirmed using debugger that vector of my channels in sensorList is changing when data are received from sensor. This looks like a problem with communication between C++ and QML.
My data handler.
ADS8588h.h
#ifndef CHANNEL_H
#define CHANNEL_H
#include <QObject>
#include <QString>
class AdsChannel {
Q_GADGET
Q_PROPERTY(QString name READ name )
Q_PROPERTY(double value READ value )
public:
AdsChannel(const QString& channelName, const double value)
: mName{channelName}, mValue{value} {}
AdsChannel() = default;
AdsChannel(const AdsChannel& rhs) = default;
AdsChannel& operator=(const AdsChannel& rhs) = default;
void setName(const QString& name) { mName = name;}
void setValue(const double value) { mValue = value; }
const QString name() const { return mName; }
double value() const { return mValue; }
private:
QString mName;
double mValue;
};
Q_DECLARE_METATYPE(AdsChannel);
#endif // CHANNEL_H
SensorList.h which collects list of channels
#ifndef SENSORLIST_H
#define SENSORLIST_H
#include "AdsChannel.h"
class SensorList : public QObject
{
Q_OBJECT
public:
explicit SensorList(QObject *parent = nullptr)
{
mItems.push_back(AdsChannel("CH1",5.0));
mItems.push_back(AdsChannel("CH2",55.0));
mItems.push_back(AdsChannel("CH3",0.0));
mItems.push_back(AdsChannel("CH4",0.0));
mItems.push_back(AdsChannel("CH5",0.0));
mItems.push_back(AdsChannel("CH6",0.0));
mItems.push_back(AdsChannel("CH7",0.0));
mItems.push_back(AdsChannel("CH8",0.0));
}
QVector<AdsChannel> items() const
{
return mItems;
}
bool setItemAt(int index, const AdsChannel &item)
{
if (index < 0 || index >= mItems.size())
return false;
const AdsChannel &oldItem = mItems.at(index);
if (item.name() == oldItem.name() && item.value() == oldItem.value())
return false;
mItems[index] = item;
return true;
}
private:
QVector<AdsChannel> mItems;
};
#endif // SENSORLIST_H
SensorDataModel.h
#ifndef SENSORDATAMODEL_H
#define SENSORDATAMODEL_H
#include <QAbstractListModel>
#include <QStandardItemModel>
#include "AdsChannel.h"
#include "SensorList.h"
class SensorList;
class SensorDataModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(SensorList *list READ list WRITE setList)
public:
explicit SensorDataModel(QObject *parent = nullptr);
enum {
NameRole = Qt::UserRole +1 ,
ValueRole
};
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = ValueRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value,
const int role = ValueRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual QHash<int, QByteArray> roleNames() const override;
SensorList *list() const;
void setList(SensorList *list);
private:
SensorList *mList;
};
#endif // SENSORDATAMODEL_H
SensorDataModel.cpp
#include "SensorDataModel.h"
#include "SensorList.h"
SensorDataModel::SensorDataModel(QObject *parent)
: QAbstractListModel(parent)
, mList(new SensorList)
{
}
int SensorDataModel::rowCount(const QModelIndex &parent) const
{
// For list models only the root node (an invalid parent) should return the list's size. For all
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
if (parent.isValid() || !mList)
return 0;
return mList->items().size();
}
QVariant SensorDataModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !mList)
return QVariant();
const AdsChannel item = mList->items().at(index.row());
switch (role) {
case NameRole:
return QVariant(item.name());
case ValueRole:
return QVariant(item.value());
}
return QVariant();
}
bool SensorDataModel::setData(const QModelIndex &indexModel, const QVariant &value,const int role)
{
// indexModel is empty as this method is invocable from c++ side. Index which is given in dataChange() signal is calculated
// based on index data which are updated
//When data arrived from a sensor this how this method is invoked.
//mData.setData(QModelIndex(),QVariant::fromValue<AdsChannel>(temp),SensorDataModel::ValueRole);
if (!mList)
return false;
auto newElement = value.value<AdsChannel>();
const auto listElements = mList->items();
AdsChannel item;
int indexFinder = 0;
for(indexFinder ; indexFinder < rowCount(); ++indexFinder)
{
if(newElement.name() == listElements.at(indexFinder).name()) {
item = listElements.at(indexFinder);
break;
}
}
switch (role) {
case NameRole:
item.setName(newElement.name());
break;
case ValueRole:
item.setValue(newElement.value());
break;
}
if (mList->setItemAt(indexFinder, item)) {
emit dataChanged (this->index(indexFinder,0), this->index(indexFinder,0), QVector<int>() << role);
//This signal is emited but never reached on QML side
return true;
}
return false;
}
Qt::ItemFlags SensorDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable;
}
QHash<int, QByteArray> SensorDataModel::roleNames() const
{
QHash<int, QByteArray> names;
names[NameRole] = "channelName";
names[ValueRole] = "value";
return names;
}
SensorList *SensorDataModel::list() const
{
return mList;
}
void SensorDataModel::setList(SensorList *list)
{
// I never use a setList() as my list is initialized throw constructor.
beginResetModel();
if (mList)
mList->disconnect(this);
mList = list;
endResetModel();
}
main.cpp
#include "ads8588h.h"
#include "SensorList.h"
#include "SensorDataModel.h"
int main(int argc, char *argv[]) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
ADS8588H driver ;
qmlRegisterType<ADS8588H>("AdsDriver", 1, 0, "AdsDriver"); // Used to some other features
qmlRegisterType<SensorDataModel>("SensorModel", 1, 0, "SensorDataModel");
qmlRegisterUncreatableType<SensorList>("SensorModel", 1, 0, "SensorList",
QStringLiteral("SensorList should not be created in QML"));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("sensorList"), driver.getSensor()->list());
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();
}
QML code using simple TEXT label to eliminate some potential bug using CANVAS, requestPaint() signals and other.
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import SensorModel 1.0
Item {
id:root
anchors.fill: parent
ListView {
implicitWidth: root.width * 0.8
implicitHeight: width
x:root.width/2 - width/2
y:170
id: customList
focus: true
spacing: 4
snapMode: ListView.SnapOneItem
highlightRangeMode: ListView.StrictlyEnforceRange
highlightMoveDuration: 250
orientation: ListView.Vertical
boundsBehavior: Flickable.StopAtBounds
model: SensorDataModel{
list: sensorList
onDataChanged: console.log("ss") //<-- This signal never appear
}
delegate: Item{
width: parent.width
height: width * 0.2
// ChannelProgressBar{
// id:licznik
// anchors.fill: parent
// labelSuffix: suffix
// labelChannel: model.channelName
// progressValue: model.value
// }
Row{
spacing: 20
Text{
id:label1
font.pixelSize: 40
text: model.channelName
}
Text{
id:label2
font.pixelSize: 40
text: model.value
}
}
}
}
}
So my question is What im doing wrong that qml did not updated data even when dataChanged() signal is emitted.
I've been struggling for a few days trying to figure out how to display some pretty simple data in a QML window. I realize there are many ways to accomplish this task, and in this case, I'd prefer to find out how to use QAbstractTableModel.
I have a row of data, each row containing two items, a name and a value (name/value or key/value pair). I've subclassed the QAbstractTableModel to pass this data to the QML. Here is the code I have so far; it is mostly based on the tutorial that can be found here (which is also very old): https://doc.qt.io/archives/4.6/itemviews-addressbook.html.
databridge.h
#include <QObject>
#include <QAbstractTableModel>
#include <QPair>
class DataBridge : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DataBridge();
explicit DataBridge(QList<QPair<QString, QString>> listOfPairs);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool insertRows(int row, int count, const QModelIndex &parent);
bool removeRows(int row, int count, const QModelIndex &parent);
Qt::ItemFlags flags(const QModelIndex &index) const;
QList<QPair<QString, QString>> getList();
signals:
public slots:
private:
QList<QPair<QString, QString>> m_listOfPairs;
};
#endif // DATABRIDGE_H
databridge.cpp
#include "databridge.h"
DataBridge::DataBridge()
{}
DataBridge::DataBridge(QList<QPair<QString, QString> > listOfPairs)
{ m_listOfPairs = listOfPairs; }
int DataBridge::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_listOfPairs.size();
}
int DataBridge::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
//Number of columns is always 2 in this kata
return 2;
}
QVariant DataBridge::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() >= m_listOfPairs.size() || index.row() < 0)
return QVariant();
if(role == Qt::DisplayRole)
{
QPair<QString, QString> pair = m_listOfPairs.at(index.row());
if(index.column() == 0)
return pair.first;
else
return pair.second;
}
return QVariant();
}
bool DataBridge::setData(const QModelIndex &index, const QVariant &value, int role)
{
//TODO
return QVariant();
}
QVariant DataBridge::headerData(int section, Qt::Orientation orientation, int role) const
{
//TODO
return QVariant();
}
bool DataBridge::insertRows(int row, int count, const QModelIndex &parent)
{
//TODO
return true;
}
bool DataBridge::removeRows(int row, int count, const QModelIndex &parent)
{
//TODO
return true;
}
Qt::ItemFlags DataBridge::flags(const QModelIndex &index) const
{
if(!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
QList<QPair<QString, QString> > DataBridge::getList()
{
return m_listOfPairs;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QPair>
#include "databridge.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QList<QPair<QString, QString>> inserter;
inserter.append(QPair<QString, QString>("0", "zero"));
inserter.append(QPair<QString, QString>("1", "one"));
inserter.append(QPair<QString, QString>("2", "two"));
inserter.append(QPair<QString, QString>("3", "three"));
inserter.append(QPair<QString, QString>("4", "four"));
DataBridge * bridge = new DataBridge(inserter);
engine.rootContext()->setContextProperty("bridge", bridge);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component{
id: myComponent
Row{
id: thisRow
Text{
text: bridge.data(index, 0)
}
Text{
text: "\t.\t.\t.\t"
}
Text{
text: bridge.data(index, 0)
}
}
}
Column{
id: thisColumn
anchors.horizontalCenter: parent.horizontalCenter
Repeater{
id: myRepeater
delegate: myComponent
model: bridge
}
}
Component.onCompleted: {
console.log("DEBUG main.qml");
}
}
Please ignore the functions with TODO in them in the databridge.cpp. I didn't include the bodies for the sake of brevity.
The first problem I'm facing is that when my repeater in the main.qml calls bridge.data(index, 0), the index is determined to be invalid in the first if statement of the data function (if(!index.isValid()). I'm not sure why this is happening. The second issue I can see, though I haven't gotten that far yet, is how can I tell the data function if I'm calling column 0 or column 1? It checks for this later in the function, and returns the pair relevant to the column requested. I'm guessing that in myComponent, I need something more specific to request the data from whichever column, but I'm not sure what that would be?
Any help with this would be immensely appreciated. Thank you in advance!
QModelIndex Documentation
Another link to the example referenced at the top
QAbstractTableModel Documentation
Qt Role Enum Documentation
I was able to find a solution, but I'm not sure if it's the correct one. I've added a function to the databridge class that takes integer input from the QML, finds a corresponding QModelIndex, and pulls the data from the model that way. This is what the code looks like:
From databridge.cpp
QString DataBridge::getThisItem(int index, int column)
{
QModelIndex currentIndex = QAbstractTableModel::index(index, column);
QString retVal = data(currentIndex, 0).toString();
return retVal;
}
Corresponding QML Repeater Change
Component{
id: myComponent
Row{
id: thisRow
Text{
text: bridge.getThisItem(index, 0)
}
Text{
text: "\t.\t.\t.\t"
}
Text{
text: bridge.getThisItem(index, 1)
}
}
}
This produces the desired output, that being:
I am interested to know if this is a good implementation, or if there is a better way to do this. Thanks again!
I've got a QML-application containing a TableView with two columns. One of them is a CheckBox. Now I created a model derived from QAbstractTableModel. Reading data for the ordinary text-column already works but how do I sync the checked-property for my CheckBox with the model?
Currently I can't even set it checked via model. You find the relevant code below.
tablemodel.cpp
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
list.append("test1");
list.append("test2");
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return list.count();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
}
QVariant TableModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= list.count())
return QVariant();
if (role == NameRole)
return list[index.row()]
else if (role== EnabledRole){
//list is not QList<QString>, its a custom class saving a String and a boolean for the checked state
return list[index.row()].isEnabled();
}
else {
return QVariant();
}
}
QHash<int, QByteArray> TableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[EnabledRole] = "enabled";
return roles;
}
tablemodel.hpp
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Roles {
NameRole = Qt::UserRole + 1,
EnabledRole
};
explicit TableModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex & parent = QModelIndex()) const;
Q_INVOKABLE QVariant data (const QModelIndex & index, int role) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<QString> list;
main.qml
TableView {
id: Table
model: TableModel
TableViewColumn {
role: "name"
}
TableViewColumn {
role: "enabled"
delegate: CheckBox {
//how to get the right state from the model
//checked: ??
}
}
}
main.cpp
QQmlApplicationEngine engine;
QQmlContext * context = new QQmlContext(engine.rootContext());
TableModel tableModel;
context->setContextProperty("tableModel",&tableModel);
QQmlComponent component(&engine, QUrl("qrc:///main.qml"));
QQuickWindow * window = qobject_cast<QQuickWindow*>(component.create(context));
window->show();
You can emit signal from qml, when clicked on checkbox; connect this signal to your model slot and do something
main.qml
TableView {
id: table
objectName: "myTable"
signal checkedChanged(int index, bool cheked)
TableViewColumn {
role: "enabled"
delegate: CheckBox {
onClicked: table.checkedChanged(styleData.row, checked);
}
}
main.cpp
QQuickItem *obj = engine.rootObjects().at(0)->findChild<QQuickItem*>(QStringLiteral("myTable"));
QObject::connect(obj,SIGNAL(checkedChanged(int,bool)),tableModel,SLOT(mySlot(int,bool)));
I've figured out how to bind a model derived from QAbstractListModel to a QML view.
But the next thing I tired does not work. If a new Item is added to the model the QML view will not update. Why is that?
DataObject.h
class DataObject {
public:
DataObject(const QString &firstName,
const QString &lastName):
first(firstName),
last(lastName) {}
QString first;
QString last;
};
SimpleListModel.h
class SimpleListModel : public QAbstractListModel
{
Q_OBJECT
enum /*class*/ Roles {
FIRST_NAME = Qt::UserRole,
LAST_NAME
};
public:
SimpleListModel(QObject *parent=0);
QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const;
QHash<int, QByteArray> roleNames() const;
void addName(QString firstName, QString lastName);
private:
Q_DISABLE_COPY(SimpleListModel);
QList<DataObject*> m_items;
};
SimpleListModel.cpp
SimpleListModel::SimpleListModel(QObject *parent) :
QAbstractListModel(parent)
{
DataObject *first = new DataObject(QString("Firstname01"), QString("Lastname01"));
DataObject *second = new DataObject(QString("Firstname02"), QString("Lastname02"));
DataObject *third = new DataObject(QString("Firstname03"), QString("Lastname03"));
m_items.append(first);
m_items.append(second);
m_items.append(third);
}
QHash<int, QByteArray> SimpleListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[/*Roles::*/FIRST_NAME] = "firstName";
roles[/*Roles::*/LAST_NAME] = "lastName";
return roles;
}
void SimpleListModel::addName(QString firstName, QString lastName)
{
DataObject *dataObject = new DataObject(firstName, lastName);
m_items.append(dataObject);
emit dataChanged(this->index(m_items.size()), this->index(m_items.size()));
}
int SimpleListModel::rowCount(const QModelIndex &) const
{
return m_items.size();
}
QVariant SimpleListModel::data(const QModelIndex &index, int role) const
{
//--- Return Null variant if index is invalid
if(!index.isValid())
return QVariant();
//--- Check bounds
if(index.row() > (m_items.size() - 1))
return QVariant();
DataObject *dobj = m_items.at(index.row());
switch (role)
{
case /*Roles::*/FIRST_NAME:
return QVariant::fromValue(dobj->first);
case /*Roles::*/LAST_NAME:
return QVariant::fromValue(dobj->last);
default:
return QVariant();
}
}
AppCore.h
class AppCore : public QObject
{
Q_OBJECT
Q_PROPERTY(SimpleListModel *simpleListModel READ simpleListModel CONSTANT)
public:
explicit AppCore(QObject *parent = 0);
SimpleListModel *simpleListModel() const;
public slots:
void addName();
private:
SimpleListModel *m_SimpleListModel;
};
AppCore.cpp
AppCore::AppCore(QObject *parent) :
QObject(parent)
{
m_SimpleListModel = new SimpleListModel(this);
}
SimpleListModel *AppCore::simpleListModel() const
{
return m_SimpleListModel;
}
void AppCore::addName()
{
m_SimpleListModel->addName("FirstnameNEW", "LastnameNEW");
}
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication a(argc, argv);
QQuickView *view = new QQuickView();
AppCore *appCore = new AppCore();
qRegisterMetaType<SimpleListModel *>("SimpleListModel");
view->engine()->rootContext()->setContextProperty("appCore", appCore);
view->setSource(QUrl::fromLocalFile("main.qml"));
view->show();
return a.exec();
}
main.qml
// ...
ListView {
id: myListView
anchors.fill: parent
delegate: myDelegate
model: appCore.simpleListModel
}
MouseArea {
anchors.fill: parent
onClicked: {
appCore.addName()
console.log('rowCount: ' + appCore.simpleListModel.rowCount())
}
}
//...
you should call beginInsertRows and endInsertRows instead of emitting the signal
void SimpleListModel::addName(QString firstName, QString lastName)
{
DataObject *dataObject = new DataObject(firstName, lastName);
// tell QT what you will be doing
beginInsertRows(ModelIndex(),m_items.size(),m_items.size());
// do it
m_items.append(dataObject);
// tell QT you are done
endInsertRows();
}
these 2 functions emit all needed signals
You're ignoring the semantics of a QAbstractItemModel. There are two kinds of signals that a model must emit:
data change signals: They must be emitted after the data was changed. A data change is a change of value of an existing item. Other changes to the model are not called data changes - the terminology here has a specific meaning.
structure change signals: They must be emitted before and after any structural change. A structural change is addition or removal of any of the items. The beginXxxYyy and endXxxYyy helper functions emit those signals.