I am trying to implement a nested comment system in a QML interface. I have a model in C++ (subclassed from QAbstractListModel) in which each item in the model returns two values: one is a QString and the other is a QVariantMap with roleName "dataMap". This works fine with a QML ListView. Now each QVariantMap contains an item "data" which further contains a QVariantList "children". Now this lists basically other QVariantMaps with the same structure. My idea to implement this was to use a recursive delegate in a QML ListView. Below is the simplest version of my code.
ListView{
id: commentsList
anchors.fill: parent
model: commentsModel
delegate: commentsDelegate
}
Component{
id: commentsDelegate
ColumnLayout{
Rectangle{
width: 600
height: 200
Text {
id: bodyText
text: dataMap.body
anchors.centerIn: parent
Component.onCompleted: console.debug(text)
}
}
ListView{
id: childList
property var childModel: dataMap.replies.data.children // QVariantList exposed to QML
x: 15
interactive: false
model: childModel
anchors.fill: parent
delegate: commentsDelegate
}
}
}
The structure of my model is the following:
class ListModel : public QAbstractListModel
{
Q_OBJECT
public:
ListModel(){}
explicit ListModel(QObject* parent =0);
~ListModel();
QHash<int, QByteArray> roleNames() const;
QVariant data(const QModelIndex & index, int role) const;
int rowCount(const QModelIndex &parent) const;
void addItem(ListItem item);
void clearModel();
private:
QList<ListItem> m_itemsList;
signals:
void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight);
};
The ListItem class is simply
class ListItem
{
public:
ListItem(QObject* parent = 0) : QObject(parent) {}
virtual ~ListItem() {}
ListItem(const QString & type, const QVariantMap & dataMap);
QString type() const;
QVariantMap dataMap() const;
private:
QString m_type;
QVariantMap m_dataMap;
Now this approach does not work for a number of reasons (one of which is that the property dataMap is accessible as data in the childModel, which is overridden by the default property data in any QML Item type). Any possible solution to this problem?
I am currently working on an application, which requires the visualization of isolated branches of massive (hundreds of millions of objects) tree. Since it is a tree, it is very similar to your problem, which is about nested models.
My solution was to make a form of abstraction. Since the tree already exists and is a completely different design layer than the GUI, I use a proxy object, which contains the model class, which attaches to the visualized tree node. The model is just an adapter for the list view to access the underlying data.
The model provides an "object" and a "type" role, which the "root" delegate uses to instantiate a UI element for the children nodes, and the object role is used to create a proxy to attach to each child, so effectively, I get indirectly nested models.
Each delegate is basically a loader (not a
Loader
QML element though), which receives a pointer to every object and its type from the model roles, so it creates a proxy for that type, and a UI elementtype + ".qml"
attached to the proxy as its data source.I cannot share any code, but hopefully you get the picture. This approach sounds a little complicated, but it offers several huge advantages:
I have found this very useful article that helped to solve the problem https://lemirep.wordpress.com/2013/04/06/a-practical-case-exposing-qt-c-models-to-qml/. The approach consists into creating another ListModel (derived from QAbstracListModel) inside the model class. In my example, I replace
QVariantMap dataMap()
with anotherListModel dataModel()
. Notice that this requires other changes too (which can be found at the link provided)