Creating tree kind of model from QAbstractItemModel or QAbstractListModel - qt

As per the below code, I need to create a model which will have N number of "System", and each "System" will have N number of "SytemDatabase" and each "SytemDatabase" will have N number of "CoresData".
This N number will come to know during the launch time of application.
struct CoresData {
int m_iCoreSpeed;
bool m_bCoreAvailable;
};
class SytemDatabase {
public:
SytemDatabase();
bool m_bDatabaseVisible;
int m_iDatabaseNumber;
QList<CoresData> m_listCoresData;
};
class Sytem {
public:
Sytem();
bool m_bSystemAvailable;
int m_iSystemNumber;
QList<SytemDatabase> m_listSytemDatabase;
};
class SytemTree : public QAbstractItemModel {
Q_OBJECT
public:
explicit SytemTree( QObject *parent = nullptr);
~SytemTree();
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
private:
void addSytemDatabase(Sytem &data);
QList<Sytem> m_listSystem;
};
Currently I have designed this model using a ListModel and its working fine. Now for some reason i need to move this model to cpp side and pass that model to qml.
Below is the QML code for reference
SytemTree.append({ "iSystemNumber": systemNumber, "bSystemAvailable": false, "SytemDatabase":[{ "iDatabaseNumber": databaseNumber, "bDatabaseVisible": false,"CoresData": []}]})
for( var lp = 0; lp < totalcoreData; ++lp) {
SytemTree.get(systemNumber).SytemDatabase.get(iDatabaseNumber).CoresData.append({ "bCoreAvailable": true, "bCoreAvailable": true, "iCoreNumber": coreNumber})
}
consider the model is developed in cpp side with reference to above cpp classes Sytem, SytemDatabase and CoresData , and the same model is passed and used like below in the qml code
Repeater {
id: repeaterRootSystem
model: SytemTree
delegate: customRectangle {---
visible: systemAvailable
value: systemNumber
----
Repeater {
id: repeaterDatabase
model: SytemTree.get(index).SytemDatabase
delegate: customRectangle {---
visible: databaseVisible
value: databaseNumber
---
Repeater {
id: repeaterCoresData
model: SytemTree.get(index).SytemDatabase.get(index).CoresData
delegate: customRectangle {--
visible: coreAvailable
value: coreNumber
speed: coreSpeed
----
}
}
}
}
}
}
I have gone through concepts QAbstractListModel, QAbstractItemModel and QAbstractTableModel.
But am looking for a model like one list model, and each list element will contain a tree like structure as mentioned above in the classes.
Requesting anyone to suggest how to create a model which will have tree like structure. and which QAbstractxxxxxmodel will be correct to implement this concept.
and in QML side i want to access the data through the index untill coresData , same like above QML code.
Thanks in advance.

Check out this similar question for some ideas.
If you're not adamant about using a QAbstractItemModel, here is a qml-based solution I've used before:
import QtQuick 2.9
import QtQuick.Window 2.2
import UISettings 1.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4 as SV
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Flickable {
id: flick
anchors.fill: parent
clip: true
contentHeight: col.implicitHeight
property var mymodel: {
"animals": {
"big": {
"land": "elephants",
"water": "whales"
},
"small": {
"land": "mice",
"water": "fish"
}
},
"plants": {
"trees": "evergreens"
}
}
Column {
id: col
Component.onCompleted: componentListView.createObject(this, {"objmodel":flick.mymodel});
}
Component {
id: componentListView
Repeater {
id: repeater
property var objmodel: ({})
model: Object.keys(objmodel)
ColumnLayout {
Layout.leftMargin: 50
Button {
property var sprite: null
text: modelData
onClicked: {
if(sprite === null) {
if(typeof objmodel[modelData] === 'object')
sprite = componentListView.createObject(parent, {"objmodel":objmodel[modelData]});
}
else
sprite.destroy()
}
}
}
}
}
}
}

Related

How to add an extra item to a QML ComboBox which is not in the model?

I have a QML ComboBox which has a QAbstractListModel attached to it. Something like this:
ComboBox {
model: customListModel
}
And I would like it to display an extra item in the drop down list which is not in the model.
For example, let's say there are two items in the customListModel: Apple and Orange.
And in the drop down list it should display the following options:
Select all
Apple
Orange
I can't add it to the model because it contains custom objects and I use this model a couple of other places in the program and it would screw up everything.
How can I add this "Select all" option to the ComboBox???
One way to do it is to create a proxy model of some sort. Here's a couple ideas:
You could derive your own QAbstractProxyModel that adds the "Select All" item to the data. This is probably the more complex option, but also the more efficient. An example of creating a proxy this way can be found here.
You could also make your proxy in QML. It would look something like this:
Combobox {
model: ListModel {
id: proxyModel
ListElement { modelData: "Select All" }
Component.onCompleted: {
for (var i = 0; i < customListModel.count; i++) {
proxyModel.append(customModel.get(i);
}
}
}
}
A solution is to customize the popup to add a header.
You could implement the entire popup component, or exploit the fact that its contentItem is a ListView and use the header property:
ListModel {
id: fruitModel
ListElement {
name: "Apple"
}
ListElement {
name: "Orange"
}
}
ComboBox {
id: comboBox
model: fruitModel
textRole: "name"
Binding {
target: comboBox.popup.contentItem
property: "header"
value: Component {
ItemDelegate {
text: "SELECT ALL"
width: ListView.view.width
onClicked: doSomething()
}
}
}
}
I found myself wanting to do something similar recently and was surprised that there's no simple way to do it; there are ways to do it but not really a dedicated API for it, not even for widgets.
I've tried both of the answers mentioned here and would like to summarise them, as well provide complete examples for each approach. My requirement was to have a "None" entry, so my answer is in that context, but you can easily replace that with "Select All".
Using QSortFilterProxyModel
The C++ code for this is based on this answer by #SvenA (thank you for sharing working code!).
Pros:
To avoid repeating myself too much: the pros for this are the absence of the cons in the other approach. For example: key navigation works, no need to touch any styling stuff, etc. These two alone are pretty big reasons why you would want to choose this approach, even if it does mean extra work writing (or copy-pasting :)) the model code (which is something you will only have to do once).
Cons:
Since you are using the 0 index for the "None" entry, you have to treat it as a special entry, unlike the -1 index, which is already established as meaning no item is selected. This means a little extra JavaScript code to handle that index being selected, but the header approach also requires this when it's clicked.
It is a lot of code for one extra entry, but again; you should only have to do it once, and then you can reuse it.
It is an extra level of indirection in terms of model operations. Assuming most ComboBox models are relatively small, this is not a problem. In practice I doubt this would be a bottleneck.
Conceptually a "None" entry could be considered a kind of metadata; i.e. it doesn't belong in the model itself, so this solution could be seen as less conceptually correct.
main.qml:
import QtQuick 2.15
import QtQuick.Controls 2.15
import App 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: "\"None\" entry (proxy) currentIndex=" + comboBox.currentIndex + " highlightedIndex=" + comboBox.highlightedIndex
ComboBox {
id: comboBox
textRole: "display"
model: ProxyModelNoneEntry {
sourceModel: MyModel {}
}
}
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QSortFilterProxyModel>
#include <QDebug>
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
QVector<QString> mData;
};
MyModel::MyModel(QObject *parent) :
QAbstractListModel(parent)
{
for (int i = 0; i < 10; ++i)
mData.append(QString::fromLatin1("Item %1").arg(i + 1));
}
int MyModel::rowCount(const QModelIndex &) const
{
return mData.size();
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return QVariant();
switch (role) {
case Qt::DisplayRole:
return mData.at(index.row());
}
return QVariant();
}
class ProxyModelNoneEntry : public QSortFilterProxyModel
{
Q_OBJECT
public:
ProxyModelNoneEntry(QString entryText = tr("(None)"), QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
private:
QString mEntryText;
};
ProxyModelNoneEntry::ProxyModelNoneEntry(QString entryText, QObject *parent) :
QSortFilterProxyModel(parent)
{
mEntryText = entryText;
}
int ProxyModelNoneEntry::rowCount(const QModelIndex &/*parent*/) const
{
return QSortFilterProxyModel::rowCount() + 1;
}
QModelIndex ProxyModelNoneEntry::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceIndex.isValid())
return QModelIndex();
else if (sourceIndex.parent().isValid())
return QModelIndex();
return createIndex(sourceIndex.row()+1, sourceIndex.column());
}
QModelIndex ProxyModelNoneEntry::mapToSource(const QModelIndex &proxyIndex) const
{
if (!proxyIndex.isValid())
return QModelIndex();
else if (proxyIndex.row() == 0)
return QModelIndex();
return sourceModel()->index(proxyIndex.row() - 1, proxyIndex.column());
}
QVariant ProxyModelNoneEntry::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return QVariant();
if (index.row() == 0) {
if (role == Qt::DisplayRole)
return mEntryText;
else
return QVariant();
}
return QSortFilterProxyModel::data(createIndex(index.row(),index.column()), role);
}
Qt::ItemFlags ProxyModelNoneEntry::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
if (index.row() == 0)
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
return QSortFilterProxyModel::flags(createIndex(index.row(),index.column()));
}
QModelIndex ProxyModelNoneEntry::index(int row, int column, const QModelIndex &/*parent*/) const
{
if (row > rowCount())
return QModelIndex();
return createIndex(row, column);
}
QModelIndex ProxyModelNoneEntry::parent(const QModelIndex &/*child*/) const
{
return QModelIndex();
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<ProxyModelNoneEntry>("App", 1, 0, "ProxyModelNoneEntry");
qmlRegisterType<MyModel>("App", 1, 0, "MyModel");
qmlRegisterAnonymousType<QAbstractItemModel>("App", 1);
QQmlApplicationEngine engine;
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();
}
#include "main.moc"
Using ListView's header
Pros:
The -1 index -- which is already established as meaning no item is selected -- can be used to refer to the "None" entry.
No need to set up a QSortFilterProxyModel-subclass in C++ and expose it to QML.
Conceptually a "None" entry could be considered a kind of metadata; i.e. it doesn't belong in the model itself, so this solution could be seen as more conceptually correct.
Cons:
Not possible to select the "None" entry with arrow key navigation. I briefly tried working around this (see commented-out code), but had no success.
Have to mimic the "current item" styling that the delegate component has. What that entails depends on the style; if you wrote the style yourself, then you could move the delegate component into its own file and reuse it for the header. However, if you you're using someone else's style, you can't do that, and will have to write it from scratch (though, you will usually only need to do this once). For example, for the Default ("Basic", in Qt 6) style it means:
Setting an appropriate font.weight.
Setting highlighted.
Setting hoverEnabled.
Have to set displayText yourself.
Since the header item isn't considered a ComboBox item, the highlightedIndex property (which is read-only) will not account for it. Can be worked around by setting highlighted to hovered in the delegate.
Have to do the following when the header is clicked:
Set currentIndex (i.e. to -1 on click).
Close the ComboBox's popup.
Emit activated() manually.
main.qml:
import QtQuick 2.0
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: "\"None\" entry (header) currentIndex=" + comboBox.currentIndex + " highlightedIndex=" + comboBox.highlightedIndex
Binding {
target: comboBox.popup.contentItem
property: "header"
value: Component {
ItemDelegate {
text: qsTr("None")
font.weight: comboBox.currentIndex === -1 ? Font.DemiBold : Font.Normal
palette.text: comboBox.palette.text
palette.highlightedText: comboBox.palette.highlightedText
highlighted: hovered
hoverEnabled: comboBox.hoverEnabled
width: ListView.view.width
onClicked: {
comboBox.currentIndex = -1
comboBox.popup.close()
comboBox.activated(-1)
}
}
}
}
ComboBox {
id: comboBox
model: 10
displayText: currentIndex === -1 ? qsTr("None") : currentText
onActivated: print("activated", index)
// Connections {
// target: comboBox.popup.contentItem.Keys
// function onUpPressed(event) { comboBox.currentIndex = comboBox.currentIndex === 0 ? -1 : comboBox.currentIndex - 1 }
// }
}
}
Conclusion
I agree with the idea that "None" and "Select All" are more metadata than model data. In that sense I prefer the header approach. In my particular use case that made me look into this, I don't allow key navigation and I have already overridden the delegate property of ComboBox, so I can reuse that code for the header.
However, if you need key navigation, or you don't want to have to reimplement the delegate for the header, the QSortFilterProxyModel approach would be more practical.

qml TableView itemdelegate not firing (using a QAbstractTableModel)

Im trying to get my first QML TableView to work in Qt 5.2 (since we are stuck on that
version right now at work) using a QAbstractTableModel on the backend.
My main issue is that for some reason the itemDelegate is never firing so
I never see anything in the View except the outline of the TableView.
I have also verified that theData_ is filled with 2 dimensional numbers
in every row/column in the constructor and I do an emit layoutChanged()
as well as an emit dataChanged() in the constructor.
I realize I have no error checking for an invalid QModelIndex in the data() call
at this time.
I also did not implement index() at all.
Also is there any need to use a ROLE here?
The data Im displaying is a single integer (as a QString) per cell, nothing more at this time.
Thanks for your help
qml:
TableView {
width: 600
height: 600
model: myModel
visible: true
itemDelegate: Rectangle {
color: "lightgray"
width: 100
height: 20
Text {
text: styleData.value
color: "black"
}
}
}
relevant code from subclassed QAbstractTableModel:
int MyModel::rowCount(const QModelIndex&) const
{
return 10;
}
int MyModel::columnCount(const QModelIndex&) const
{
return 3;
}
QVariant MyModel::data(const QModelIndex& index, int role) const
{
const int row = index.row();
const int col = index.column();
return QString("%1").arg(this->theData_[col][row]);
}
Before Qt 5.12 there was only a TableView component that belongs to Qt QuickControl 1 that only supports a list type model where each column reflects the information of a role so this is probably your problem since you have not created any TableViewColumn. On the other hand, as of >= Qt5.12, another TableView already exists, which it supports as a table type model.
mymodel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractListModel>
struct Data{
int number;
QString text;
};
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum CustomRoles{
NumberRole = Qt::UserRole,
TextRole
};
explicit MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
private:
QList<Data> m_data;
};
#endif // MYMODEL_H
mymodel.cpp
#include "mymodel.h"
MyModel::MyModel(QObject *parent)
: QAbstractListModel(parent)
{
// for test
for(int i=0; i< 10; i++){
Data d{i, QString::number(i)};
m_data.push_back(d);
}
}
int MyModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_data.count();
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if(role == NumberRole)
return m_data.at(index.row()).number;
if(role == TextRole)
return m_data.at(index.row()).text;
return QVariant();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NumberRole] = "number";
roles[TextRole] = "text";
return roles;
}
main.qml
// ...
TableView {
width: 600
height: 600
model: myModel
visible: true
TableViewColumn {
role: "number"
title: "Number"
width: 100
}
TableViewColumn {
role: "text"
title: "Text"
width: 200
}
itemDelegate: Rectangle {
color: "lightgray"
width: 100
height: 20
Text {
text: styleData.value
color: "black"
}
}
}
// ...

Is this the Minimum Viable TreeView Model in QML?

I'm making a folding list of three items: "Hey", "Whats", and "Up?". I want to put it into a tree view. I know this list will only ever contain these three items. Therefore, I would like to know how to "nest" these items together.
I know there are implementation for agile systems that support adding and removing parent/child objects, finding indexes... powerful models. However, I literally only need to display these items in an expandable/collapsable view. Here is what I've read through relating to C++ and QAbstractItemModels:
QML Treeview
QML QAbstractItemModel
This Question by My_Cat_Jessica
This question by kavaliero which was based on:
This 'working' example by Qt themselves (Doesn't actually work for TreeView. Works for QTreeView though!)
Here is the simplest viable code to implement a treeview with model:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: mywindow
visible: true
width: 640
height: 480
TreeView {
id: treeview
anchors.fill: parent
TableViewColumn {
title: "Phrase"
role: "phrase"
}
model: phraseModel
}
ListModel {
id: phraseModel
ListElement { phrase: "Hey"; }
ListElement { phrase: "What's"; }
ListElement { phrase: "Up?"; }
}
}
I would like for the output to result in a nested stack like this:
Hey
What's
Up?
But I am getting everything in a single column all aligned with each other:
Hey
What's
Up?
I know I haven't assigned parents, and I'm not entirely sure how to do that - But I'm not even sure if that's what needs done to this code. So my question is this: What's the final step missing to stack these three elements into an expandable/collapsible view?
There is no native QML model that can use the TreeView, so I have implemented a model that tries to be generic:
TreeElement
// treeelement.h
#ifndef TreeElement_H
#define TreeElement_H
#include <QObject>
#include <QQmlListProperty>
class TreeElement : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
Q_CLASSINFO("DefaultProperty", "items")
TreeElement(QObject *parent = Q_NULLPTR);
Q_INVOKABLE TreeElement *parentItem() const;
bool insertItem(TreeElement *item, int pos = -1);
QQmlListProperty<TreeElement> items();
TreeElement *child(int index) const;
void clear();
Q_INVOKABLE int pos() const;
Q_INVOKABLE int count() const;
private:
static void appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value);
static int countElement(QQmlListProperty<TreeElement> *property);
static void clearElement(QQmlListProperty<TreeElement> *property);
static TreeElement *atElement(QQmlListProperty<TreeElement> *property, int index);
QList<TreeElement *> m_childs;
TreeElement *m_parent;
};
#endif // TreeElement_H
// treeelement.cpp
#include "treeelement.h"
TreeElement::TreeElement(QObject *parent) :
QObject(parent),
m_parent(nullptr) {}
TreeElement *TreeElement::parentItem() const{
return m_parent;
}
QQmlListProperty<TreeElement> TreeElement::items(){
return QQmlListProperty<TreeElement> (this,
this,
&TreeElement::appendElement,
&TreeElement::countElement,
&TreeElement::atElement,
&TreeElement::clearElement);
}
TreeElement *TreeElement::child(int index) const{
if(index < 0 || index >= m_childs.length())
return nullptr;
return m_childs.at(index);
}
void TreeElement::clear(){
qDeleteAll(m_childs);
m_childs.clear();
}
bool TreeElement::insertItem(TreeElement *item, int pos){
if(pos > m_childs.count())
return false;
if(pos < 0)
pos = m_childs.count();
item->m_parent = this;
item->setParent(this);
m_childs.insert(pos, item);
return true;
}
int TreeElement::pos() const{
TreeElement *parent = parentItem();
if(parent)
return parent->m_childs.indexOf(const_cast<TreeElement *>(this));
return 0;
}
int TreeElement::count() const{
return m_childs.size();
}
void TreeElement::appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
parent->insertItem(value);
}
int TreeElement::countElement(QQmlListProperty<TreeElement> *property){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
return parent->count();
}
void TreeElement::clearElement(QQmlListProperty<TreeElement> *property){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
parent->clear();
}
TreeElement *TreeElement::atElement(QQmlListProperty<TreeElement> *property, int index){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
if(index < 0 || index >= parent->count())
return nullptr;
return parent->child(index);
}
TreeModel
// treemodel.h
#ifndef TreeModel_H
#define TreeModel_H
#include <QAbstractItemModel>
#include <QQmlListProperty>
class TreeElement;
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
Q_CLASSINFO("DefaultProperty", "items")
TreeModel(QObject *parent = Q_NULLPTR);
~TreeModel() override;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) 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;
QQmlListProperty<TreeElement> items();
QVariantList roles() const;
void setRoles(const QVariantList &roles);
Q_INVOKABLE QModelIndex indexFromElement(TreeElement *item);
Q_INVOKABLE bool insertElement(TreeElement *item, const QModelIndex &parent = QModelIndex(), int pos = -1);
TreeElement *elementFromIndex(const QModelIndex &index) const;
private:
TreeElement *m_root;
QHash<int, QByteArray> m_roles;
signals:
void rolesChanged();
};
#endif // TreeModel_H
// treemodel.cpp
#include "treemodel.h"
#include "treeelement.h"
TreeModel::TreeModel(QObject *parent) :
QAbstractItemModel(parent){
m_root = new TreeElement;
}
TreeModel::~TreeModel(){
delete m_root;
}
QHash<int, QByteArray> TreeModel::roleNames() const{
return m_roles;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const{
if (!index.isValid())
return QVariant();
TreeElement *item = static_cast<TreeElement*>(index.internalPointer());
QByteArray roleName = m_roles[role];
QVariant name = item->property(roleName.data());
return name;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const{
if (!index.isValid())
return nullptr;
return QAbstractItemModel::flags(index);
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const{
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeElement *parentItem = elementFromIndex(parent);
TreeElement *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const{
if (!index.isValid())
return QModelIndex();
TreeElement *childItem = static_cast<TreeElement*>(index.internalPointer());
TreeElement *parentItem = static_cast<TreeElement *>(childItem->parentItem());
if (parentItem == m_root)
return QModelIndex();
return createIndex(parentItem->pos(), 0, parentItem);
}
int TreeModel::rowCount(const QModelIndex &parent) const{
if (parent.column() > 0)
return 0;
TreeElement *parentItem = elementFromIndex(parent);
return parentItem->count();
}
int TreeModel::columnCount(const QModelIndex &parent) const{
Q_UNUSED(parent)
return 1;
}
QQmlListProperty<TreeElement> TreeModel::items(){
return m_root->items();
}
QVariantList TreeModel::roles() const{
QVariantList list;
QHashIterator<int, QByteArray> i(m_roles);
while (i.hasNext()) {
i.next();
list.append(i.value());
}
return list;
}
void TreeModel::setRoles(const QVariantList &roles){
static int nextRole = Qt::UserRole + 1;
for(QVariant role : roles) {
m_roles.insert(nextRole, role.toByteArray());
nextRole ++;
}
emit rolesChanged();
}
QModelIndex TreeModel::indexFromElement(TreeElement *item){
QVector<int> positions;
QModelIndex result;
if(item) {
do{
int pos = item->pos();
positions.append(pos);
item = item->parentItem();
} while(item != nullptr);
for (int i = positions.size() - 2; i >= 0 ; i--)
result = index(positions[i], 0, result);
}
return result;
}
bool TreeModel::insertElement(TreeElement *item, const QModelIndex &parent, int pos){
TreeElement *parentElement = elementFromIndex(parent);
if(pos >= parentElement->count())
return false;
if(pos < 0)
pos = parentElement->count();
beginInsertRows(parent, pos, pos);
bool retValue = parentElement->insertItem(item, pos);
endInsertRows();
return retValue;
}
TreeElement *TreeModel::elementFromIndex(const QModelIndex &index) const{
if(index.isValid())
return static_cast<TreeElement *>(index.internalPointer());
return m_root;
}
main.cpp
#include "treemodel.h"
#include "treeelement.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
static void registertypes(){
qmlRegisterType<TreeElement>("foo", 1, 0, "TreeElement");
qmlRegisterType<TreeModel>("foo", 1, 0, "TreeModel");
}
Q_COREAPP_STARTUP_FUNCTION(registertypes)
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
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();
}
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.4
import foo 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TreeModel {
id: treemodel
roles: ["phrase"]
TreeElement{
property string phrase: "Hey"
TreeElement{
property string phrase: "What's"
TreeElement{
property string phrase: "Up?"
}
}
}
}
TreeView {
anchors.fill: parent
model: treemodel
TableViewColumn {
title: "Name"
role: "phrase"
width: 200
}
}
}
Output:
The complete example you find here
I created a collapsible frame in QML (a group box with a title and a content). If you are sure that you will never change the structure, you can use for your purpose:
I simplified the code by removing the useless parts (animations, decorations, etc.). So the code below could be improved. But, I kept the idea:
// CollapsibleGroupBox.qml
Item {
property alias contentItem: content.contentItem;
property string title: ""
Item {
id: titleBar
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 30
Row {
anchors.fill: parent
CheckBox {
Layout.alignment: Qt.AlignLeft
id: expand
checked: true;
}
Text {
Layout.alignment: Qt.AlignLeft
text: title
}
}
}
Pane {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: titleBar.bottom
anchors.bottom: parent.bottom
topPadding: 0
visible: expand.checked
id: content
}
}
// Main.qml
Item {
height: 500
width: 500
CollapsibleGroupBox {
anchors.fill: parent
title: "Hey!"
contentItem: CollapsibleGroupBox {
title: "What's"
contentItem: CollapsibleGroupBox {
title: "up?"
}
}
}
}
You will get:
You can replace the checkbox by a MouseArea, also.
I've also created a model that only uses QML components:
import QtQuick 2.9
import QtQuick.Window 2.2
import UISettings 1.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4 as SV
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Flickable {
id: flick
anchors.fill: parent
clip: true
contentHeight: col.implicitHeight
property var mymodel: {
"animals": {
"big": {
"land": "elephants",
"water": "whales"
},
"small": {
"land": "mice",
"water": "fish"
}
},
"plants": {
"trees": "evergreens"
}
}
Column {
id: col
Component.onCompleted: componentListView.createObject(this, {"objmodel":flick.mymodel});
}
Component {
id: componentListView
Repeater {
id: repeater
property var objmodel: ({})
model: Object.keys(objmodel)
ColumnLayout {
Layout.leftMargin: 50
Button {
property var sprite: null
text: modelData
onClicked: {
if(sprite === null) {
if(typeof objmodel[modelData] === 'object')
sprite = componentListView.createObject(parent, {"objmodel":objmodel[modelData]});
}
else
sprite.destroy()
}
}
}
}
}
}
}

returning a custom QObject subclass from QAbstractListModel and using it in a ListView

How can I return a custom QObject sub-class from QAbstractListModel and use it in a QML ListView.
I tried to return the objects as display role and I use in my qml display.property to access properties, it works fine but I saw on some posts people using model as the qobject from qml and accessing properties as model.property. Am I missing something?.
Another question: If I want to expose the object at the ListView level and using it to set some other panel like a master-view detail is exposing the role (in my case display) as a variant property in the delegate and setting it at the listview level with the onCurrentItemChanged signal is the correct way to do it ??
this is what I am trying but it does not work:
#ifndef NOTE_H
#define NOTE_H
#include <QObject>
class Note : public QObject
{
Q_OBJECT
Q_PROPERTY(QString note READ note WRITE setNote NOTIFY noteChanged)
Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged)
QString m_note;
int m_id;
public:
explicit Note(QObject *parent = 0);
Note(QString note, int id, QObject *parent = 0);
QString note() const
{
return m_note;
}
int id() const
{
return m_id;
}
signals:
void noteChanged(QString note);
void idChanged(int id);
public slots:
void setNote(QString note)
{
if (m_note == note)
return;
m_note = note;
emit noteChanged(note);
}
void setId(int id)
{
if (m_id == id)
return;
m_id = id;
emit idChanged(id);
}
};
#endif // NOTE_H
the view model:
#ifndef NOTESVIEWMODEL_H
#define NOTESVIEWMODEL_H
#include <QAbstractListModel>
#include <QVector>
#include "note.h"
class NotesViewModel : public QAbstractListModel
{
Q_OBJECT
QVector<Note*> notes;
public:
NotesViewModel();
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override;
};
#endif // NOTESVIEWMODEL_H
implementation of the view model:
NotesViewModel::NotesViewModel()
{
notes.append(new Note("note 1", 1));
notes.append(new Note("note 2", 2));
notes.append(new Note("note 3", 3));
notes.append(new Note("note 4", 4));
notes.append(new Note("note 5", 5));
}
QVariant NotesViewModel::data(const QModelIndex &index, int role) const
{
qDebug() << "fetching data : " << index.row();
if(!index.isValid()) return QVariant();
if(index.row() >= 5) return QVariant();
if(role == Qt::DisplayRole)
return QVariant::fromValue(notes[index.row()]);
return QVariant();
}
int NotesViewModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return notes.count();
}
Since you've already answered your second question, let me take on the first one, i.e. display.propertyName vs model.propertyName
Basically the first one is just a short hand for model.display.propertyName, i.e. the "display" property of the model's data at the given index is being accessed. In your case that returns an object, which has properties on its on.
The model.propertyName could also be written as just propertyName, meaning the model's data() method is called with "role" being the numerical equivalent for "propertyName".
This mapping from "propertyName" to its numerical equivalent is done with the QAbstractItemModel::roleNames() method.
Its default implementation has some base mappings such as mapping "display" to Qt::DisplayRole.
I played a little with the qml ListView trying to figure out how to expose the currentItem data to the outside world. This is how I managed to do it, maybe it will be of use for newbies like me.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListModel {
id: modell
ListElement {
fname: "houssem"
age: 26
}
ListElement {
fname: "Anna"
age: 26
}
ListElement {
fname: "Nicole"
age: 26
}
ListElement {
fname: "Adam"
age: 27
}
}
ListView {
id: lv
height: 100
width: 200
clip: true
model: modell
property string selectedName: currentItem.name
delegate: Component {
Item {
id: mainItem
width: ListView.view.width
height: 80
property string name: fname
Text {
text: "name " + fname + " age " + age
}
MouseArea {
anchors.fill: parent
onClicked: mainItem.ListView.view.currentIndex = index
}
}
}
}
Text {
anchors.right: parent.right
text: lv.selectedName
}
}
As you can see in the code, to expose the data of currentItem I had just to declare properties in the delegate Item (name property of type string in the present example) and then bind a property on ListView (selectedName in this example) to the currentItem.property, this way the property on the ListView will be updated automatically when I select other items from the list and I will have access to this items form other Items of the UI.

QtQuick TableView not working with C++-QAbstractTableModel

I am trying to get the Qt model/view-architecture working with a QML-View, but for whatever reason it is only partially working.
What works:
rowCount
data
roleNames
Not working:
columnCount (the method is called, but seems to have no effect, as long is it is > 0)
headerData (is this actually supposed to set the column headers? All examples set the headers in QML)
flags
setData
What I am trying to do (for some weeks already), is to create a simple ApplicationView with a TableView and a C++-model, which is editable by the view.
Right now only a whole row is selectable, not a single cell. The table data doesn't seem to be editable at all. Can anyone give me a hint?
main.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TableView {
model: theModel
TableViewColumn {
role: "nameRole"
width: 75
}
TableViewColumn {
role: "ageRole"
width: 50
}
}
}
ModelItem.hpp
#ifndef MODELITEM
#define MODELITEM
#include <QString>
struct ModelItem {
ModelItem(QString name_, int age_)
: name(name_), age(age_) {}
QString name;
int age;
};
#endif // MODELITEM
TableModel.hpp
#ifndef TABLEMODEL_HPP
#define TABLEMODEL_HPP
#include <QAbstractTableModel>
#include "ModelItem.hpp"
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
TableModel(QObject *parent = 0);
//works
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
//does not work
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
enum Roles {
NameRole = Qt::UserRole + 1,
AgeRole
};
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
private:
QList<ModelItem*> items;
};
#endif // TABLEMODEL_HPP
TableModel.cpp
#include "TableModel.hpp"
TableModel::TableModel(QObject *parent)
: QAbstractTableModel(parent) {
items.append(new ModelItem("Hugo",33));
items.append(new ModelItem("Egon",34));
items.append(new ModelItem("Balder",66));
qDebug("TableModel initialisiert");
}
int TableModel::columnCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
qDebug("columnCount");
return 2;
}
int TableModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
qDebug("rowCount");
return items.count();
}
QVariant TableModel::data(const QModelIndex &index, int role) const {
qDebug("data");
switch (role) {
case NameRole: return items[index.row()]->name;
case AgeRole: return items[index.row()]->age;
}
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const {
qDebug("headerData");
switch (role) {
case NameRole: return "1";
case AgeRole: return "2";
};
return QVariant();
}
QHash<int, QByteArray> TableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "nameRole";
roles[AgeRole] = "ageRole";
qDebug("roleNames initialised");
return roles;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const {
qDebug("--flags called--");
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
qDebug("setData called");
switch (role) {
case NameRole: items[index.row()]->name = value.toString();
case AgeRole: items[index.row()]->age = value.toInt();
}
emit dataChanged(index, index);
return true;
}
main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "TableModel.hpp"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
TableModel model;
engine.rootContext()->setContextProperty("theModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
columnCount and rowCount called in QAbstractItemModel::index method, which is called by TableView before data method.
QModelIndex QAbstractTableModel::index(int row, int column, const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ? createIndex(row, column, 0) : QModelIndex();
}
bool QAbstractItemModel::hasIndex(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column < 0)
return false;
return row < rowCount(parent) && column < columnCount(parent);
}
columnCount has no effect, as long is it is > 0, because TableView called the index method with column always equal to 0.
headerData and flags did not affect the QML TableView. You can only set the headers on the QML side. To create an editable TableView, you should implement your custom itemDelegate
main.qml
ApplicationWindow {
visible: true
id: root
Component {
id: editableDelegate
Item {
Text {
width: parent.width
anchors.margins: 4
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
elide: styleData.elideMode
text: styleData.value !== undefined ? styleData.value : ""
color: styleData.textColor
visible: !styleData.selected
}
Loader {
id: loaderEditor
anchors.fill: parent
anchors.margins: 4
Connections {
target: loaderEditor.item
onEditingFinished: {
theModel.setData(styleData.row, styleData.column, loaderEditor.item.text)
}
}
sourceComponent: styleData.selected ? editor : null
Component {
id: editor
TextInput {
id: textinput
color: styleData.textColor
text: styleData.value
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: textinput.forceActiveFocus()
}
}
}
}
}
}
TableView {
id: table
anchors.fill: parent
model: theModel
itemDelegate: editableDelegate;
TableViewColumn {
role: "nameRole"
width: 75
title: "name"
}
TableViewColumn {
role: "ageRole"
width: 50
title: "age"
}
}
}
To apply the changes to your model, you should implement the setData method, like this:
TableModel.h
bool setData(const QModelIndex &index, const QVariant &value, int role) {
switch (role) {
case NameRole: items[index.row()]->name = value.toString(); break;
case AgeRole: items[index.row()]->age = value.toInt(); break;
}
emit dataChanged(index, index);
return true;
}
Q_INVOKABLE bool setData(int row, int column, const QVariant value)
{
int role = Qt::UserRole + 1 + column;
return setData(index(row,0), value, role);
}
The above answer covers things nicely. I just want to add a couple of notes:
in QML you can simply do model.role = value within an item delegate instead of having to call setData() by hand
several QAbstractItemModel methods, including setData(), have been made Q_INVOKABLE in Qt 5.5: https://codereview.qt-project.org/#/c/107171/

Resources