Search code examples
qtqabstractitemmodel

QTreeView and QAbstractItemModel


I'm new to Qt and I'm trying to make a simple tree based on a flat (or from a sqlite table) file paths list (not from FS) like this:

C:\Parent1\Child1\Leaf1.jpg
C:\Parent1\Child1\Leaf2.jpg
C:\Parent1\Child1\Leaf3.jpg
C:\Parent1\Child2\Leaf1.jpg
C:\Parent1\Child2\Leaf2.jpg
C:\Parent2\Child1\Leaf1.jpg
C:\Parent2\Child1\Leaf2.jpg
...
D:\....
...

I'd like to display this as a tree view (e.g. a file explorer).

I looked into QAbstractItemModel but I have some difficulties to build the tree. My idea is to split each of the path using '\' and check if the branches already exist before adding them. If the branches exist, I have to find the good parent to add this new child.

I'm using a simple tree example but I have real difficulties to implement my model.

void MyTreeModel::setupModelData(TreeItem *parent)
{
    QList<TreeItem*> parents;
    parents << parent;

    int number = 0;

    QString path = "mydb_path";
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(path);
    if(db.open())
    {
        QSqlQuery query("SELECT path FROM file");
        int idPath = query.record().indexOf("path");

        while (query.next())
        {
           QString name = query.value(idPath).toString();
           int id_file = query.value(idIdx).toInt();

           QStringList nodeString = name.split("\\", QString::SkipEmptyParts);


           for(int node = 0; node < nodeString.count(); ++node)
           {
            // I don't know what to do now... Should I build a lookup table with a parent id for each node before to populate my TreeItems ?

           }
        }
    }
    //...
}

Any tips ?


Solution

  • Using a QAbstractModelItem is not intuitive. But it looks like your biggest problem is actually modeling your list of paths into a tree of elements. There are two tasks :

    1. Process your input data to obtain a data structure close your conceptual interpretation of the data (you know they are path.)
    2. Use this data structure under your QAbstractItemModel implementation.

    Step 1: An actual tree implementation

    You need to implement a tree first. Something like

    struct mytree
    {
        static std::shared_ptr<mytree> frompath(QString path);
        static std::shared_ptr<mytree> merge(std::shared_ptr<mytree> t1, std::shared_ptr<mytree> t2);
    
        //may need helpers : is leaf, etc, or just access to children
        QString value;
        std::list<std::shared_ptr<mytree> > childrens_;
    
        mytree(); //construct empty tree
    };
    

    Where the value is a file name or folder name.

    • frompath builds the tree from a single entry.C:\Parent2\Child1\Leaf2.jpg becomes

      C->Parent2->Child1->Leaf2.jpg
      
    • merge take two existing trees and construct a single one

      C->Parent2->Child1->Leaf1.jpg
      C->Parent2->Child2->Leaf1.jpg
      

      becomes

      C->Parent2->{Child1->Leaf1.jpg, Child2->Leaf1.jpg}
      

    Step 2: The model

    Once you have that list, you need to implement at least the following methods:

    QModelIndex parent(const QModelIndex & index) const;
    QModelIndex index(int row, int column, const QModelIndex & parent) const;
    
    int columnCount(const QModelIndex & parent) const;
    int rowCount(const QModelIndex & parent) const;
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
    

    Some tips:

    1. An invalid model index is QModelIndex(). A top level item has QModelIndex
    2. You have to assign one or more QModelIndex to an object mytree
    3. You have only one column for any parent, because there is only one data (the filename).
    4. The number of rows is the number of children.
    5. QModelIndex are created using createIndex

    Now to be able to implement those method you need to add 3 fields to the tree :

    • A reference to the parent
    • the position of the current element in the parent list
    • an id field, to uniquely identify a node. You can use uint qHash(const QString & key) on the path.

    The new tree have the fields

    struct mytree
    {
        //removed methods;
        QString path;          eg C:\Parent1\Child1
        QString value;         eg Child1
        unsigned int id;       eg: qHash(path)
    
        int pos; //my position in the parent list. this is the row in the model
    
        std::shared_ptr<mytree> parent;      
        std::list<std::shared_ptr<mytree> > childrens;
    };
    

    You need to be able to quickly get a mytree given its id. Which means your internal data structure in the model will be

    std::map<unsigned int, std::shared_pr<mytree> > items_by_id;
    std::shared_pr<mytree> root_item;
    

    Now you have all you need to implement the methods above. This is just for demo so don't take this code for granted

    std::shared_pr<mytree> find_elt_helper(const QModelIndex & index) const
    {   
       auto iter = items_by_id.find(index.internalId());
       if(iter == items_by_id.end())
           return std::shared_pr<mytree>();
    
       return iter->second;
    }
    
    
    QModelIndex parent(const QModelIndex & index) const
    {
       if(!index.isValid())
           return QModelIndex();
    
       std::shared_pr<mytree> index_item = find_elt_helper(index);
    
       return index_item ? create_index(index_item->parent->pos, 0, index_item->parent->id) : QModelIndex();
    }
    
    QModelIndex index(int row, int column, const QModelIndex & parent) const
    {
        std::shared_pr<mytree> parent_item = !parent.isValid() ? root_item : find_elt_helper(parent);
    
        std::shared_pr<mytree> item;
        if(row >= parent_item.children.size())
           return QModelIndex();
    
        item = parent_item.children[row];
    
        return create_index(row, 0, item->id);
    }