QML Model data by index - c++

I have QAbstractListModel based model...
class RecordModel : public QAbstractListModel { ... };
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("recordModel", &model);
// QML
recordModel.get(0).name // now work
How to get a model data by index and role name?
... Solution:
// C++
class RecordModel : public QAbstractListModel
{
Q_OBJECT
Q_ENUMS(Roles)
public:
// ...
Q_INVOKABLE QVariant data(int i, int role) const
{
return data(index(i, 0), role);
}
};
// QML
recordModel.data(0, RecordModel.NameRole);

You should only use a Q_INVOKABLE method for specific functionalities that you want to be able to call from QML. Unless you somehow want to access model data from without access to the delegate of your model, you should always use the more proper model<->delegate way of getting the data.
Since you're inheriting from QAbstractListModel, it'll work like QAbstractItemModel.
Declare roles as shown:
enum Roles {
RoleA = Qt::UserRole + 1,
RoleB = Qt::UserRole + 2
};
Override this method to allow QML access using roles:
QHash<int, QByteArray> RecordModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[RoleA] = _Ut("roleA");
roles[RoleB] = _Ut("roleB");
return roles;
}
Override this method to return data when QML tries to access:
QVariant RecordModel::data(const QModelIndex &modelIndex, int role) const
{
QVariant rv;
int index = modelIndex.row();
switch( role ) {
case RoleA:
rv = "A";
break;
case RoleB:
rv = "B";
break;
default:
DASSERT(FALSE); // Unexpected role.
break;
}
return rv;
}
Then in QML you'll simply use "roleA" and "roleB" in the delegates of the QML Element that uses this model to access the data.
References:
http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html
http://qt-project.org/doc/qt-5.0/qtcore/qabstractlistmodel.html

You can use Q_INVOKABLE method:
Q_INVOKABLE QVariant getRecord(int iIndex)
{
return QVariant::fromValue<CRecord*>((CRecord*)this->at(iIndex));
}
And then in QML:
recordModel.getRecord(0)
You will just need to declare a metatype:
Q_DECLARE_METATYPE( RecordModel* )
And somewhere in main.cpp:
qmlRegisterType<RecordModel>("PrivateComponents", 1, 0, "RecordModel");
qmlRegisterType<CRecord>("PrivateComponents", 1, 0, "CRecord");

Related

How to access Qt::DisplayRole and specify columns in TableView

The QFileSystemModel has the following data function:
Variant QFileSystemModel::data(const QModelIndex &index, int role) const
{
Q_D(const QFileSystemModel);
if (!index.isValid() || index.model() != this)
return QVariant();
switch (role) {
case Qt::EditRole:
case Qt::DisplayRole:
switch (index.column()) {
case 0: return d->displayName(index);
case 1: return d->size(index);
case 2: return d->type(index);
case 3: return d->time(index);
I wonder how I can access the DisplayRole and specify the column I want in a QML TableViewColumn.
I want to use it in
TableView {
model: fileSystemModel
TableViewColumn {
role: //what comes here?
}
}
If you want to access within a delegate you have to use styleData.index that returns the QModelIndex and pass it the value of the role, in this case Qt::DisplayRole that according to the docs is 0:
view.model.data(styleData.index, 0)
if you know the row, column and QModelIndex of parent:
view.model.data(view.model.index(row, colum, ix_parent), 0)
If you plan to reuse the model several times, you could consider sub-classing QFileSystemModel and add a custom role:
class FileSystemModel : public QFileSystemModel
{
public:
explicit FileSystemModel(QObject *parent = nullptr) : QFileSystemModel(parent) {}
enum Roles {
FileSizeRole = Qt::UserRole + 1
};
QVariant data(const QModelIndex &index, int role) const
{
switch (role) {
case FileSizeRole:
return QFileSystemModel::data(this->index(index.row(), 1, index.parent()),
Qt::DisplayRole);
default:
return QFileSystemModel::data(index, role);
}
}
QHash<int, QByteArray> roleNames() const
{
auto result = QFileSystemModel::roleNames();
result.insert(FileSizeRole, "fileSize");
return result;
}
};
This way, you can simply refer to the role by its name:
TreeView {
model: fsModel
anchors.fill: parent
TableViewColumn {
role: "display"
}
TableViewColumn {
role: "fileSize"
}
}
QFileSystemModel inherits from QAbstractItemModel, which has a method called roleNames(), that returns a QHash with the names of the default Roles (e.g. DysplayRole, DecorationRole, EditRole etc..) see:https://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames. To be accurate, QFileSystemModel defines its own roles on top of the QAbstracItemModel ones. see: https://doc.qt.io/qt-5/qfilesystemmodel.html#Roles-enum
So if you didn't define any custom role, then you can simply refer to the display role with it's default name (display) in your QML file . Like this:
TableView {
model: fileSystemModel
TableViewColumn {
role: "display"
}
}
That said, if you define custom roles, you have to override that roleNames() method, to give names to the new roles you defined. In that case, in order to keep consistency with the parent class, you should call first QAbstractItemModel::roleNames() method (in your case QFileSystemModel::roleNames()), and then set the new rolenames in the returned QHash. Here is an example for a login item where I defined host, username and password roles:
QHash<int, QByteArray> LoginModel::roleNames() const
{
QHash<int,QByteArray> names = QAbstractItemModel::roleNames();
names[HostRole] = "host";
names[UsernameRole] = "username";
names[PasswordRole] = "password";
return names;
}
You can also simply use model.display or just display to get DisplayRole from any model.

Qt accessing model data outside ItemDelegate

I have some model class that inherits QAbstractListModel:
VehiclesModel.h:
class VehiclesModel : public QAbstractListModel {
Q_OBJECT
public:
enum Roles {
ImagePathRole = Qt::UserRole + 1, // QString
NameRole // QString
};
virtual int rowCount(const QModelIndex & parent = QModelIndex()) const override { ... }
virtual QVariant data(const QModelIndex & index, int role) const override { ... }
virtual QHash<int, QByteArray> roleNames() const override {
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles[ImagePathRole] = "imagePath";
roles[NameRole] = "name";
return roles;
}
};
main.cpp:
#include "VehiclesModel.h"
int main(int argc, char * argv[]) {
QGuiApplication app(argc, argv);
VehiclesModel vehiclesModel;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("vehiclesModel", &vehiclesModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
And ComboBox that displays this model:
main.qml:
ComboBox {
id: control
model: vehiclesModel
delegate: ItemDelegate {
contentItem: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
highlighted: control.highlightedIndex == index
}
contentItem: RowLayout {
Image {
source: ??imagePath??
}
Label {
text: ??name??
}
}
}
I want to customize the ComboBox to show vehicle image and name. I can access to model data from ItemDelegate but how to access to model data outside the ItemDelegate? For example I want to access current index data (ImagePathRole and NameRole) to display vehicle image and name in contentItem.
Is it possible to do it without calling QAbstractListModel methods directly (i.e. index() and data() methods) and making them Q_INVOKABLE?
Not in any sort of a decent built-in way at the present time, unfortunately, this is something I've found to be lacking for quite a while, and I've considered implementing something for this in the QML models functionality, but I haven't yet had the time to do so.
For the time being, you can either do it yourself (like you're discussing), at the cost of type-safety and so on, or (the way I've typically tackled this before), you can create a QObject subclass to represent a single item in the model (ItemDataThing or whatever you choose to call it); provide it with a source model & index, properties, and let it represent a single instance of data from the model.
Something like:
class ImageDataThing : public QObject
{
Q_OBJECT
Q_PROPERTY(QString imagePath READ imagePath NOTIFY imagePathChanged)
Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
public:
QString imagePath() const;
QAbstractItemModel *model() const;
void setModel(const QAbstractItemModel *newModel);
int index() const;
void setIndex(int newIndex);
signals:
void imagePathChanged(const QString &imagePath);
void modelChanged(QAbstractItemModel *model);
void indexChanged(int indexChanged);
};
... and in your implementation, whenever the model is set, hook the change signals (e.g. rowsInserted, rowsRemoved, ...) to alter the stored index (if provided) to keep it mapped to the correct place in the model.
In the model data getters (here, imagePath for instance), access the model instance (using the index) to grab the data out, and return it.
This has the obvious disadvantage of being a lot of boilerplate, but on the other hand, it's easy-to-write code if you are familiar with models, type-safe, and one could autogenerate it fairly easily.
You could create your own function to get data from the model, like the one I'm currently using,
VehiclesModel.h:
public slots:
int size() const; // to access from QML javascript
QVariant getData(int index, int role); // to access from QML javascript
VehiclesModel.cpp:
int VehiclesModel::size() const {
return m_list.size();
}
QVariant VehiclesModel::getData(int index, int role) {
if (index < 0 || index >= m_list.count())
return QVariant();
switch (role) {
case ImagePathRole:
return ...
break;
default:
break;
}
}
Shameless plug for my SortFilterProxyModel library.
The problem you are asking is actually a head-scratching scenario. I've found a way to do it somewhat correctly but it's kinda complicated and involves an external library. In my solution we filter the source model to only expose the element corresponding to the current index of the combo box and instantiate a delegate for this element and use it as the contentItem of the ComboBox.
This has the advantage of not having to modify your model and keeping in synch with your model changes.
import SortFilterProxyModel 0.2 // from https://github.com/oKcerG/SortFilterProxyModel
import QtQml 2.2
/*
...
*/
ComboBox {
id: control
model: vehiclesModel
delegate: ItemDelegate {
contentItem: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
highlighted: control.highlightedIndex == index
}
contentItem: { currentIndex; return selectedInstantiator.object; } // use currentIndex to force the binding reevaluation. When the model changes, the instantiator doesn't notify object has changed
Instantiator {
id: selectedInstantiator
model: SortFilterProxyModel {
sourceModel: control.model
filters: IndexFilter {
minimumIndex: control.currentIndex
maximumIndex: control.currentIndex
}
}
delegate: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
}
}
I strongly suggest to look at the Qt QML Tricks library made by Thomas Boutroue:
https://gitlab.com/qt-qml-libraries-4-me/qt-qml-tricks-ng
More specific the QQmlObjectListModel (from the Qt QML Models) could do the trick for you.
Expanding with using the Qt Super-Macros, it reduces overhead writing setters/getters!
These macros basically expand to a Q_PROPERTY, resulting in accessibility from QML, and add definition of a setter, getter and private variable.
Usage in your specific case this could look something like this, quickly written down, not validated (check using the correct index for referencing the model):
VehicleItem.h:
#include <QObject>
#include "QQmlVarPropertyHelpers.h" // Include library Qt Super-Macros
class VehicleItem : public QObject {
Q_OBJECT
QML_WRITABLE_VAR_PROPERTY(QString, imagePath)
QML_WRITABLE_VAR_PROPERTY(QString, name)
public:
explicit VehicleItem(QString imagePath, QString name, QObject* parent=0)
: QObject (parent)
, m_imagePath (imagePath)
, m_name (name)
{}
};
VehiclesModel.h:
#include <QObject>
#include "QQmlObjectListModel.h" // Include library Qt QML Models
#include "VehicleItem.h"
class VehiclesModel : public QObject {
Q_OBJECT
QML_OBJMODEL_PROPERTY(VehicleItem, modelList)
public:
explicit VehiclesModel(QObject *parent = 0);
};
VehiclesModel.c:
#include "VehiclesModel.h"
VehiclesModel::VehiclesModel(QObject *parent) :
QObject(parent), m_modelList(new QQmlObjectListModel<VehicleItem>())
{}
main.c (remains the same):
#include "VehiclesModel.h"
int main(int argc, char * argv[]) {
QGuiApplication app(argc, argv);
VehiclesModel vehiclesModel;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("vehiclesModel", &vehiclesModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml:
ComboBox {
id: control
model: vehiclesModel
delegate: ItemDelegate {
contentItem: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
highlighted: control.highlightedIndex == index
}
contentItem: RowLayout {
Image {
source: vehiclesModel.modelList.get(index).imagePath
}
Label {
text: vehiclesModel.modelList.get(index).name
}
}
}
As modelList (and also imagePath and name) is expanded by the macro to a Q_PROPERTY, it is accessible from QML side.
For the ins-and-outs of this library, be sure to check Thomas Boutroue's lightning talk at the QtWS2015: https://www.youtube.com/watch?v=96XAaH97XYo

How to change the data model of the QSqlQueryModel's subclass at run time?

Can I manage QSqlQueryModel's subclass at run time calling its methods from QML code and updating (changing) the current model? (The data is send to TableView ) How can I do it?
My QSqlQueryModel's subclass:
class SqlQueryModel : public QSqlQueryModel
{
Q_OBJECT
public:
explicit SqlQueryModel(QObject *parent = 0);
void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());
void setQuery(const QSqlQuery &query);
QVariant data(const QModelIndex &index, int role) const;
QHash<int, QByteArray> roleNames() const { return m_roleNames; }
private:
void generateRoleNames();
QHash<int, QByteArray> m_roleNames;
};
main.cpp:
// ...
SqlQueryModel sqlQueryModel;
QQuickView view;
QQmlContext *context = view.rootContext();
context->setContextProperty("sqlQueryModel", &sqlQueryModel);
// ...
For example, I need to call Q_INVOKABLE method changeModel() at run time that changes the current model and updates it with parameterized SELECT query:
void SqlQueryModel::changeModel(const int someValue)
{
QString statement;
QSqlQuery query;
statement = "SELECT * FROM 'tablename' WHERE some_field = ?;";
query.prepare(statement);
query.addBindValue(someValue);
query.exec();
setQuery(query);
}
And as result we get update the data into TableView:
TableView {
id: view
model: sqlQueryModel
TableViewColumn {
title: "1st field"
role: "someValue"
delegate: Text {
text: styleData.value
}
}
TableViewColumn {
title: "2nd field"
role: "oneMoreValue"
delegate: Text {
text: styleData.value
}
}
}
// ...
onSomeSignal: {
// query like this:
sqlQueryModel.changeModel(someValue);
}
Is it possible to do it using QSqlQueryModel? Please help me to solve this problem.
UPD: Perhaps, it's necessary to call function like qmlRegisterType() in order to allow using Q_INVOKABLE methods of SqlQueryModel class from the outside (from QML) and then initialize SqlQueryModel as a type within QML file. Thereafter we can connect our new SqlQueryModel type as TableView's data model.
UPD: I don't need to edit the data stored in the database. I want to be able to change SELECT query from one to similar.
It's very strange but the right answer is in the question.
The implementation described there works correctly. Perhaps, it went badly because of typo or defect that sneaked into project's source code.

Data is not displayed in TableView

I try to build simple TableView with model from C++. My table has as many rows and columns, as return rowCount and columnCount method. This means, model is 'connected' with view, but in each cell does not display the message: 'Some data'
here is my code:
class PlaylistModel : public QAbstractTableModel
{
Q_OBJECT
public:
PlaylistModel(QObject *parent=0): QAbstractTableModel(parent), rows(0){}
int rowCount(const QModelIndex & /*parent*/) const
{
return 5;
}
int columnCount(const QModelIndex & /*parent*/) const
{
return 3;
}
QModelIndex index(int row, int column, const QModelIndex &parent) const {
return createIndex(row, column);
}
QModelIndex parent(const QModelIndex &child) const {
return child.parent();
}
QVariant data(const QModelIndex &index, int role) const{
if (role == Qt::DisplayRole)
{
return QString("Some data");
}
return QVariant();
}
(...)
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
PlaylistModel plModel(0);
engine.rootContext()->setContextProperty("myModel", &plModel);
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
and qml
TableView {
id: trackList
width: 100; height: 100
model: myModel
TableViewColumn { role: "id"; title: "Id"; width: 30 }
TableViewColumn { role: "name"; title: "Name"; width: 100}
TableViewColumn { role: "duration"; title: "Duration"; width: 20 }
}
Where I make a mistake?
Short answer
Because your QML is not asking for Qt::DisplayRole. Change your TableVievColumn to
TableViewColumn { role: "display"; title: "xxx"; width: 20 }
The QML is now asking for Qt::DisplayRole, and "Some data" is shown in this column.
Long answer
QML is asking for three user-defined roles: "id", "name", and "duration". However, the three roles are not bulit-in roles. Therefore you need to implement the three roles in your model class.
First, you should provide a set of roles to the model. The model returns data to views using QAbstractItemModel::data function. The type of role is int, we can write an enum in the model class:
class PlaylistModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum MyTableRoles
{
IdRole = Qt::UserRole + 1,
NameRole,
DurationRole
}
//...
};
Now, in the data function, returns the corresponding value any time the view is asking:
QVariant PlaylistModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()){return QVariant();}
switch(role)
{
case IdRole:
return GetIdFromMyTable(index);
case NameRole:
return GetNameFromMyTable(index);
case DurationRole:
return GetDurationFromMyTable(index);
}
//...
return QVariant();
}
Next, provide a string-to-int mapping for each role. The role in the model is in type of int, however in the QML the role is type of string. (see the role property in TableViewColumn.) Therefore we should provide a string-to-int mapping for each role so the QML can correctly asking for the required data. The mapping should be provided in QAbstractItemModel::roleNames():
QHash<int, QByteArray> PlaylistModel::roleNames() const
{
QHash<int, QByteArray> roleNameMap;
roleNameMap[IdRole] = "id";
roleNameMap[NameRole] = "name";
roleNameMap[DurationRole] = "duration";
return roleNameMap;
}
Finally your table in QML now can display the things you want.
Some references
When subclassing QAbstractTableModel,
you must implement rowCount(), columnCount(), and data(). Default implementations of the index() and parent() functions are provided by QAbstractTableModel.
When using C++ models in QML,
The roles of a QAbstractItemModel subclass can be exposed to QML by reimplementing QAbstractItemModel::roleNames().
And if you do not reimplement the roleNames function, you can use only the default roles declared in QAbstractItemModel::roleNames. And this is the reason why the short answer works.
You did not implemented index method.
According to documentation,
When subclassing QAbstractItemModel, at the very least you must
implement index(), parent(), rowCount(), columnCount(), and data().
These functions are used in all read-only models, and form the basis
of editable models.
in other words, You have to implement your own low-level item management, AbstractItemModel will not do it for you. You should create indexes with createIndex and destroy them when nesessary etc.
If you don't want to play these games and just want to implement your own quick&dirty model, consider subclassing QStandardItemModel.

QML view wont update when adding a new item to a QAbstractListModel based model

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.