internalId
. In the case of a property editor, you can save the property group identifier in internalId
.QModelIndex
and the element of the internal data structure.QFileInfo
) had to wrap service information. In most cases, without this it is impossible to do. If the domain data already supports the hierarchy, you can use it, but I have never met the case where the source data would contain all the necessary information. struct FilesystemModel::NodeInfo { QFileInfo fileInfo; // QVector<NodeInfo> children; // NodeInfo* parent; // bool mapped; // . };
typedef QVector<NodeInfo> NodeInfoList; NodeInfoList _nodes; //
enum Columns { RamificationColumn, // , , . // QTreeView NameColumn = RamificationColumn, // ModificationDateColumn, // SizeColumn, // ColumnCount // };
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const; virtual QModelIndex parent(const QModelIndex &child) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
NodeInfo
in the internalPointer
index. In most cases, this is exactly what they do. In the implementation of index
in no case can not return non-existent indexes. You do not need to rely on the fact that no one will request such an index. To check the existence of an index there is a very convenient function hasIndex
. QModelIndex FilesystemModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } if (!parent.isValid()) { // return createIndex(row, column, const_cast<NodeInfo*>(&_nodes[row])); } NodeInfo* parentInfo = static_cast<NodeInfo*>(parent.internalPointer()); return createIndex(row, column, &parentInfo->children[row]); }
parent
things are a little more complicated. Despite the fact that at a given index you can always find the NodeInfo
parent element, to create an index of the parent element it is also necessary to know its position among the "brothers". QModelIndex FilesystemModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return QModelIndex(); } NodeInfo* childInfo = static_cast<NodeInfo*>(child.internalPointer()); NodeInfo* parentInfo = childInfo->parent; if (parentInfo != 0) { // parent return createIndex(findRow(parentInfo), RamificationColumn, parentInfo); } else { return QModelIndex(); } } int FilesystemModel::findRow(const NodeInfo *nodeInfo) const { const NodeInfoList& parentInfoChildren = nodeInfo->parent != 0 ? nodeInfo->parent->children: _nodes; NodeInfoList::const_iterator position = qFind(parentInfoChildren, *nodeInfo); return std::distance(parentInfoChildren.begin(), position); }
rowCount
and columnCount
trivial: in the first case, we can always determine the number of child nodes from NodeInfo::children::size
, and the number of columns is fixed. int FilesystemModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return _nodes.size(); } const NodeInfo* parentInfo = static_cast<const NodeInfo*>(parent.internalPointer()); return parentInfo->children.size(); } int FilesystemModel::columnCount(const QModelIndex &) const { return ColumnCount; }
data
also not difficult, all the necessary information is obtained from QFileInfo
. At a minimum, you need to implement support for Qt::DisplayRole
roles to display text in view
and Qt::EditRole
, if editing is provided. Data received from the model with the role of Qt::EditRole
will be loaded into the editor. Moreover, the data that the model returns when queried with Qt::DisplayRole
and Qt::EditRole
may differ. For example, we will display files without extensions, and edit - with the extension. QVariant FilesystemModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const NodeInfo* nodeInfo = static_cast<NodeInfo*>(index.internalPointer()); const QFileInfo& fileInfo = nodeInfo->fileInfo; switch (index.column()) { case NameColumn: return nameData(fileInfo, role); case ModificationDateColumn: if (role == Qt::DisplayRole) { return fileInfo.lastModified(); } break; case SizeColumn: if (role == Qt::DisplayRole) { return fileInfo.isDir()? QVariant(): fileInfo.size(); } break; default: break; } return QVariant(); } QVariant FilesystemModel::nameData(const QFileInfo &fileInfo, int role) const { switch (role) { case Qt::EditRole: return fileInfo.fileName(); case Qt::DisplayRole: if (fileInfo.isRoot()) { return fileInfo.absoluteFilePath(); } else if (fileInfo.isDir()){ return fileInfo.fileName(); } else { return fileInfo.completeBaseName(); } default: return QVariant(); } Q_UNREACHABLE(); }
void FilesystemModel::fetchRootDirectory() { const QFileInfoList drives = QDir::drives(); qCopy(drives.begin(), drives.end(), std::back_inserter(_nodes)); } FilesystemModel::FilesystemModel(QObject *parent) : QAbstractItemModel(parent) { fetchRootDirectory(); }
QTreeView
and see the result. bool canFetchMore(const QModelIndex &parent) const; void fetchMore(const QModelIndex &parent);
true
, when data for a given parent element can be loaded, and the second one can actually load data.NodeInfo::mapped
comes in handy NodeInfo::mapped
. Data can be loaded when mapped == false
. bool FilesystemModel::canFetchMore(const QModelIndex &parent) const { if (!parent.isValid()) { return false; } const NodeInfo* parentInfo = static_cast<const NodeInfo*>(parent.internalPointer()); return !parentInfo->mapped; }
QDir
. In this case, do not forget to use beginInsertRows
and endInsertRows
when changing the number of rows. Unfortunately, QTreeView
loads only when trying to expand a node, and does not try to load new data when scrolling through the list. Therefore, nothing remains as to download the entire list of child nodes in its entirety. You can correct this behavior, perhaps, by creating your own display component. void FilesystemModel::fetchMore(const QModelIndex &parent) { NodeInfo* parentInfo = static_cast<NodeInfo*>(parent.internalPointer()); const QFileInfo& fileInfo = parentInfo->fileInfo; QDir dir = QDir(fileInfo.absoluteFilePath()); QFileInfoList children = dir.entryInfoList(QStringList(), QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name); beginInsertRows(parent, 0, children.size() - 1); parentInfo->children.reserve(children.size()); for (const QFileInfo& entry: children) { NodeInfo nodeInfo(entry, parentInfo); nodeInfo.mapped = !entry.isDir(); parentInfo->children.push_back(std::move(nodeInfo)); } parentInfo->mapped = true; endInsertRows(); }
QTreeView
uses the hasChildren
function to check if a node has children, and assumes that only those nodes that have children can be expanded. hasChildren
, by default, returns true
only when the number of rows and the number of columns for the parent node is greater than 0.hasChildren
function so that it returns true
for the specified node, when it definitely has or can have (when mapped ==false
) child nodes. bool FilesystemModel::hasChildren(const QModelIndex &parent) const { if (parent.isValid()) { const NodeInfo* parentInfo = static_cast<const NodeInfo*>(parent.internalPointer()); Q_ASSERT(parentInfo != 0); if (!parentInfo->mapped) { return true;//QDir(parentInfo->fileInfo.absoluteFilePath()).count() > 0; -- , } } return QAbstractItemModel::hasChildren(parent); }
Source: https://habr.com/ru/post/172187/
All Articles