
First of all, it is necessary to determine the internal data structure . Here I would highlight 2 main areas: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