QTreeView and QAbstractItemModel

霸气de小男生 提交于 2019-12-13 02:13:09

问题


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 ?


回答1:


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);
}



回答2:


Many thanks UmNyobe to have cleared my mind... I was a bit confused. Here my implementation:

treeitem.h

#ifndef TREEITEM_H
#define TREEITEM_H


#include <QList>
#include <QVariant>

class TreeItem
{
public:
    explicit TreeItem(const QList<QVariant> &data, TreeItem *parentItem = 0, unsigned int id = 0);
    ~TreeItem();

    void appendChild(TreeItem *child);

    TreeItem *child(int row);
    int childCount() const;
    int columnCount() const;
    QVariant data(int column) const;
    int row() const;

    unsigned int getIndex(){return _id;};

    TreeItem *parentItem();

private:

    QList<TreeItem*> m_childItems;
    QList<QVariant> m_itemData;
    TreeItem *m_parentItem;
    unsigned int _id;
};


#endif // TREEITEM_H

treeitem.cpp

#include <QStringList>

#include "treeitem.h"

TreeItem::TreeItem(const QList<QVariant> &data, TreeItem *parent, unsigned int id)
{
    m_parentItem = parent;
    m_itemData = data;
    _id = id;
}

TreeItem::~TreeItem()
{
    qDeleteAll(m_childItems);
}

void TreeItem::appendChild(TreeItem *item)
{
    m_childItems.append(item);
}

TreeItem *TreeItem::child(int row)
{
    return m_childItems.value(row);
}

int TreeItem::childCount() const
{
    return m_childItems.count();
}

int TreeItem::columnCount() const
{
    return m_itemData.count();
}

QVariant TreeItem::data(int column) const
{
    return m_itemData.value(column);
}

TreeItem *TreeItem::parentItem()
{
    return m_parentItem;
}

int TreeItem::row() const
{
    if (m_parentItem)
        return m_parentItem->m_childItems.indexOf(const_cast<TreeItem*>(this));

    return 0;
}

restoretreemodel.h

#ifndef RESTORETREEMODEL_H
#define RESTORETREEMODEL_H

#include "treeitem.h"
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>


class RestoreTreeModel : public QAbstractItemModel
{
    Q_OBJECT
public:
    explicit RestoreTreeModel(QObject* parent=0);
    ~RestoreTreeModel();

    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(TreeItem *parent);
    int findNode(unsigned int& hash, const QList<TreeItem*>& tList);

    TreeItem *rootItem;

};

#endif // RESTORETREEMODEL_H

restoretreemodel.cpp

#include "restoretreemodel.h"
#include <QStringList>
#include <QtSql>


RestoreTreeModel::RestoreTreeModel(QObject *parent)
    : QAbstractItemModel(parent)
{
    QList<QVariant> rootData;
    rootData << "Nom" << "Nombre d'éléments";
    rootItem = new TreeItem(rootData);
    setupModelData(rootItem);
}

RestoreTreeModel::~RestoreTreeModel()
{
    delete rootItem;
}

int RestoreTreeModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
    else
        return rootItem->columnCount();
}

QVariant RestoreTreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role != Qt::DisplayRole)
        return QVariant();

    TreeItem *item = static_cast<TreeItem*>(index.internalPointer());

    return item->data(index.column());
}

Qt::ItemFlags RestoreTreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;

    return QAbstractItemModel::flags(index);
}

QVariant RestoreTreeModel::headerData(int section, Qt::Orientation orientation,
                               int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
        return rootItem->data(section);

    return QVariant();
}

QModelIndex RestoreTreeModel::index(int row, int column, const QModelIndex &parent)
            const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    TreeItem *parentItem;

    if (!parent.isValid())
        parentItem = rootItem;
    else
        parentItem = static_cast<TreeItem*>(parent.internalPointer());

    TreeItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}

QModelIndex RestoreTreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
    TreeItem *parentItem = childItem->parentItem();

    if (parentItem == rootItem)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}

int RestoreTreeModel::rowCount(const QModelIndex &parent) const
{
    TreeItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = rootItem;
    else
        parentItem = static_cast<TreeItem*>(parent.internalPointer());

    return parentItem->childCount();
}

int RestoreTreeModel::findNode(unsigned int& hash, const QList<TreeItem*>& tList)
{
    for(int idx = 0; idx < tList.size(); ++idx)
    {
        unsigned int z = tList.at(idx)->getIndex();
        if(z == hash)
            return idx;
    }

    return -1;
}



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

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

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

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

           QString temppath = "";

           int lastidx = 0;
           for(int node = 0; node < nodeString.count(); ++node)
           {
               temppath += nodeString.at(node);
               if(node != nodeString.count() - 1)
                   temppath += "\\";

               unsigned int hash = qHash(temppath);
               QList<QVariant> columnData;

               columnData << nodeString.at(node);

               int idx = findNode(hash, parents);

               if(idx != -1)
               {
                    lastidx = idx;
               }
               else
               {
                   QString sQuery =  "";
                   if(node == nodeString.count() - 1)
                   {
                       sQuery += "SELECT count(*) FROM version WHERE id_file=";
                       sQuery += QString::number(id_file);
                       sQuery += ";";
                   }
                   else
                   {

                       sQuery += "SELECT count(*) FROM file WHERE path like '";
                       sQuery += temppath;
                       sQuery += "%';";
                   }


                   int nChild = 0;
                   QSqlQuery query2(sQuery);

                   if(query2.next())
                        nChild = query2.value(0).toInt();

                   columnData << nChild;

                   if(lastidx != -1)
                   {
                       parents.at(lastidx)->appendChild(new TreeItem(columnData, parents.at(lastidx), hash));
                       parents <<  parents.at(lastidx)->child( parents.at(lastidx)->childCount()-1);
                       lastidx = -1;
                   }
                   else
                   {
                       parents.last()->appendChild(new TreeItem(columnData, parents.last(), hash));
                       parents <<  parents.last()->child( parents.last()->childCount()-1);
                   }
               }
           }
        }
    }
}

I think it can be improved but it does the job. I'm waitting to any suggestions.



来源:https://stackoverflow.com/questions/36351165/qtreeview-and-qabstractitemmodel

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!