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.
Related
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).
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.
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.
The following code is from a Qt demo. This is a model for QTreeView.
TreeItem class below represent each node in tree, it can have child nodes.
class TreeItem
{
public:
explicit TreeItem(const QList<QVariant> &data, TreeItem *parentItem = 0);
~TreeItem();
void appendChild(TreeItem *child);
TreeItem *child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
int row() const;
TreeItem *parentItem();
private:
QList<TreeItem*> m_childItems;
QList<QVariant> m_itemData;
TreeItem *m_parentItem;
};
TreeModel class below is the main model. It only contains one root node which contain all other child nodes.
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TreeModel(const QString &data, QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
private:
void setupModelData(const QStringList &lines, TreeItem *parent);
TreeItem *rootItem;
};
I am using QML and I can get this model displayed in tree however I also want to display in a ListView.
For the ListView, I only want to display one tier at one time (1st childrens). When user click on any of the item, it should clear and show that item children. How do I do that?
My Qml code is below. It displays all first tier children which is great but I need to show children when user clicks on an item. My thought is need extract the sub model and point to it, but how?
Item {
width: parent.width
height: parent.height
ListView {
//anchors.top: myImage.bottom
anchors.fill: parent
id: list
spacing: 4
model: treeModel
delegate: listDelegate
}
Component {
id: listDelegate
Rectangle
{
id: delegateRect
height: image.height
width: 500
Image {
id: image
source: "qrc:/icons/resources/element.ico"
}
Text {
id: t
anchors.left: image.right
anchors.leftMargin: 20
anchors.centerIn: delegateRect
text: title + "/" + summary
//text: display
}
MouseArea {
anchors.fill: parent
onClicked: {
list.currentIndex = index
console.log("Item clicked, index = " + index)
// I think I should change model here to sub model but how do I do it?
//list.model = treeModel.data(index)
}
}
}
}
}
You should have a look at the QAbstractProxyModel and derived classes in the Qt documentation http://doc.qt.io/qt-5/qabstractproxymodel.html.
A proxy model is used to map model indexes (if you want to modify the layout, or do sorting/filtering on data) and do data processing (modify the data returned from the source models data method).
What you need to do is to add a property to select the new root (the item that should be the root item of the extracted model) and implement the two methods mapFromSource(const QModelIndex &) and mapToSource(const QModelIndex &). These methods map the model index given to the view (and only valid in your proxy model) to a model index that is valid for the source model based on the currently set root property (and vice versa).
In addition you should also reimplement the roleNames() method to forward the rolenames defined by the source model to be able to access the data from inside QML.
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.