5
votes

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?

2
It's not clear what is the structure of your c++ model. Could you edit your question and include the missing code?Krzysztof Piekutowski
I have edited my question adding the structure of my c++ modelreckless

2 Answers

2
votes

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 element type + ".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:

  • the heavy Qt objects are only created for the objects which are needed
  • multiple UI elements can share the same proxy and model
  • a single delegate can create an arbitrary number of completely different UI elements
  • it works for a tree which has arbitrary number of different types with different properties, and it uses only two roles to achieve it. Each object gets its unique UI element, with access to all the data of the underlying object through the proxy. Good luck achieving that with dynamic roles
  • the proxy is also used to inform every UI of changes in the data of every underlying object data member
1
votes

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 another ListModel dataModel(). Notice that this requires other changes too (which can be found at the link provided)