How to access Qt::DisplayRole and specify columns in TableView - c++

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.

Related

QSqlQueryModel - override function data not being called

I am trying to populate a TableView in QML with some information retrieved from a MySQL database.
I am able to connect to the database using QSqlQuery, but when trying to use QSqlQueryModel, it is not working (results obtained in the image at the end). I've been debugging the application, but the overrided function data and the overrided function roleNames of the model are never called.
This is how my model file looks like: tableModel.h
#ifndef TABLEMODEL_H
#define TABLEMODEL_H
#include <QObject>
#include <QtQml/qqml.h>
#include <QSqlQueryModel>
#include <source/database/mySQL/mySqlQueries.h>
class TableModel : public QSqlQueryModel {
Q_OBJECT
public:
// List all the roles that will be used in the TableView
enum Roles {
CHROM_ROLE = Qt::UserRole + 1,
POS_ROLE,
ID_ROLE,
REF_ROLE,
ALT_ROLE,
QUAL_ROLE
};
explicit TableModel(QObject *parent = 0);
// Override the method that will return the data
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
protected:
/* hashed table of roles for speakers.
* The method used in the wilds of the base class QAbstractItemModel,
* from which inherits the class QSqlQueryModel
* */
QHash<int, QByteArray> roleNames() const override;
};
#endif // TABLEMODEL_H
Here, the implementation of the file: tableModel.cpp
#include "tableModel.h"
TableModel::TableModel(QObject *parent) : QSqlQueryModel(parent) {
}
// The method for obtaining data from the model
QVariant TableModel::data( const QModelIndex & index, int role) const {
switch (role) {
case CHROM_ROLE:
return QString("%1, %2").arg(index.column()).arg(index.row());
case POS_ROLE:
return QString("%1, %2").arg(index.column()).arg(index.row());
case ID_ROLE:
return QString("%1, %2").arg(index.column()).arg(index.row());
case REF_ROLE:
return QString("%1, %2").arg(index.column()).arg(index.row());
case ALT_ROLE:
return QString("%1, %2").arg(index.column()).arg(index.row());
case QUAL_ROLE:
return QString("%1, %2").arg(index.column()).arg(index.row());
default:
break;
}
return QVariant();
}
QHash<int, QByteArray> TableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[CHROM_ROLE] = "CHROM_ROLE";
roles[POS_ROLE] = "POS_ROLE";
roles[ID_ROLE] = "ID_ROLE";
roles[REF_ROLE] = "REF_ROLE";
roles[ALT_ROLE] = "ALT_ROLE";
roles[QUAL_ROLE] = "QUAL_ROLE";
return roles;
}
As you can see, I've overrided data and roleNames. None of those functions are called when executing it. (Right now, data should return the row and column numbers, not the real data).
The corresponding part of the qml file containing the TableView object is the following:
import QtQuick.Controls 2.4
import QtQuick.Controls 1.4 as Controls
import QtQuick.Window 2.11
import Qt.labs.qmlmodels 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Title")
Controls.TableView {
id: tableview
width: root.width * 0.8
height: root.height * 0.8
anchors.centerIn: parent
clip: true
Controls.TableViewColumn {
role: "CHROM_ROLE" // These roles are roles names coincide with a C ++ model
title: "#Chrom"
}
Controls.TableViewColumn {
role: "POS_ROLE" // These roles are roles names coincide with a C ++ model
title: "Pos."
}
Controls.TableViewColumn {
role: "ID_ROLE" // These roles are roles names coincide with a C ++ model
title: "ID"
}
Controls.TableViewColumn {
role: "REF_ROLE" // These roles are roles names coincide with a C ++ model
title: "Ref."
}
Controls.TableViewColumn {
role: "ALT_ROLE" // These roles are roles names coincide with a C ++ model
title: "Alt."
}
Controls.TableViewColumn {
role: "QUAL_ROLE" // These roles are roles names coincide with a C ++ model
title: "Qual."
}
// We set the model in the TableView
model: TableModel
}
}
As you can see, the roles for each of the TableViewColumns, corresponds with the roles in the cpp file.
The pro file contains the following line (besides, SQL queries are working):
QT += sql
In the main.cpp file, I am instantiating everything like this.:
QGuiApplication app(argc, argv);
qmlRegisterType<TableModel>("TableModel", 1, 0, "TableModel");
TableModel tableModel;
MySqlConnector db;
db.connectToDB("localhost", "dbname", "user", "Password");
db.open();
// I've tested that the query is working using just QSqlQuery and retrieves info from database
tableModel.setQuery(MySQLQueries::queryBodyOfVCF.arg(1), db.db);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("TableModel", &tableModel);
const QUrl url(QStringLiteral("qrc:/views/MasterView.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();
When I execute it, this is what I am getting:
The expected results should be something like this (In the real database there is only one row, and as mentioned in the comments, tableModel.rowCount() returns 1 as expected). This is an example of real data that should be displayed:
chrom pos ref alt qual id
ctg1 9 A C, G 100 rs001
ctg3 12 C T 100 rs002
Is there something wrong with my implementation? I think that I may be me misunderstanding some concept, or something missing in the .h file or the .cpp file.
The problem is caused because you are using the name of the item: TableModel, the solution is to change the name of the context-property:
engine.rootContext()->setContextProperty("tableModel", &tableModel);
model: tableModel
On the other hand, I recommend using the model that is implemented in another post where I generalize the logic.

QAbstractListModel returning unexpcted result on Qt::DisplayRole

I have created a very minimal QAbstractListModel with two roles, display and test
InvoiceTabModel::InvoiceTabModel(QObject *parent): QAbstractListModel(parent)
{
}
QVariant InvoiceTabModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(index)
if(role == 123)
return QVariant("testRole");
return QVariant("displayRole");
}
int InvoiceTabModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 3;
}
QHash<int, QByteArray> InvoiceTabModel::roleNames() const
{
return { {Qt::DisplayRole, "display"}, {123, "test"} };
}
I attached this model to a repeater
Repeater{
id: invoiceTab
anchors.fill: parent
model: invoice.tabmodel
Button{
width: 100
height: parent.height
text: test
//text: display
}
}
The problem is that when I use display role the text is displayed as 2, but when I use test in qml, the string is displaying correctly
Using test Role
When using the display role
Where does this 2 come from?
display is a property of Button.
When using data coming from a model, always use the model. prefix to disambiguate (model.display).

QAbstractTableModel::header data and QML TableView

I have subclasses the QAbstractTableModel and provided the headerData override:
/**
* #brief Obtains the header (columns) names.
* #param section: column number.
* #param orientation: Accepts only horizontal.
* #param role: Accepts only display.
* #return The column header text in case all params are valid.
* Otherwise empty QVariant.
*/
QVariant CVarTableModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation != Qt::Horizontal)
return QVariant();
if (section >= static_cast<int>(Columns::ZCOUNT))
return QVariant();
return QVariant::fromValue(static_cast<Columns>(section));
}
I am trying to figure out how to make my QML TableView component utilize this function. Is there a way to do this automatically?
Make your method invokbale from QML by using the macro Q_INVOKABLE. Then, use it in your QML as any other method:
class Model: public QStandardItemModel
{
public:
Model(QObject* parent=nullptr): QStandardItemModel(parent)
{
setColumnCount(2);
setRowCount(2);
}
Q_INVOKABLE virtual QVariant headerData(int section,
Qt::Orientation orientation,
int role=Qt::DisplayRole) const override
{
qDebug() << section << orientation << role;
if (role != Qt::DisplayRole)
return QVariant();
if (section == 0)
return "First Column";
return "Not first column";
}
};
// In main.cpp
Model* model = new Model();
QQuickView *view = new QQuickView;
view->rootContext()->setContextProperty("myModel", model);
view->setSource(QUrl("qrc:/main.qml"));
view->show();
TableView {
TableViewColumn {
role: "title"
title: myModel.headerData(0, Qt.Vertical);
width: 100
}
TableViewColumn {
role: "author"
title: myModel.headerData(1, Qt.Vertical);
width: 200
}
model: myModel
}
I found today a different solution to this I did not know: https://doc.qt.io/qt-6/qml-qtquick-controls2-horizontalheaderview.html
Column{
HorizontalHeaderView{
syncView: tableView
}
TableView {
id: tableView
model: myModel
}
}
The HorizontalHeaderView will use what you provide in your model with headerData.

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 Model data by index

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");