问题
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 :
- Process your input data to obtain a data structure close your conceptual interpretation of the data (you know they are path.)
- 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
becomesC->Parent2->Child1->Leaf2.jpg
merge
take two existing trees and construct a single oneC->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:
- An invalid model index is
QModelIndex()
. A top level item hasQModelIndex
- You have to assign one or more
QModelIndex
to an objectmytree
- You have only one column for any parent, because there is only one data (the filename).
- The number of rows is the number of children.
- 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