上一篇實現了扁平的二維表模型,本文將進階到真正的樹狀結構。核心在於設計節點類 TreeNode 來維護父子關係,並修改模型類以正確處理非空 parent 索引。
每個節點需保存:自身數據、父節點指針、子節點列表。
class TreeNode {
public:
explicit TreeNode();
TreeNode* parent() const;
void setParent(TreeNode* p); // 根節點父節點為 nullptr
void appendNode(TreeNode* node); // 添加子節點
void removeNode(int row); // 移除子節點
void setData(int role, QVariant value); // 設置角色數據
QVariant data(int role) const; // 獲取角色數據
QList<TreeNode*> childs; // 子節點列表(建議私有+訪問器)
private:
TreeNode* mParent = nullptr;
QHash<int, QVariant> mRecord; // 單行數據:role -> value
};
TreeNode* TreeNode::parent() const { return mParent; }
void TreeNode::setParent(TreeNode* p) { mParent = p; }
void TreeNode::appendNode(TreeNode* node) { childs.append(node); }
void TreeNode::removeNode(int row) { childs.removeAt(row); }
QVariant TreeNode::data(int role) { return mRecord[role]; }
void TreeNode::setData(int role, QVariant value) { mRecord[role] = value; }
保留原有角色枚舉與 roleNames(),將 mRecords 替換為根節點列表 mRootEntrys,並新增輔助方法 _addEntryNode。
class SqlMenuEntry : public QAbstractItemModel, public QQmlParserStatus {
Q_OBJECT
public:
// ... 角色枚舉、五大虛函數聲明、roleNames() ...
private:
QHash<int, QByteArray> mRoleNames;
QList<TreeNode*> mRootEntrys; // 根節點列表
void _addEntryNode(TreeNode* node, TreeNode* parent = nullptr);
};
void SqlMenuEntry::_addEntryNode(TreeNode* node, TreeNode* parent) {
if (parent == nullptr) {
mRootEntrys.append(node); // 作為根節點
} else {
node->setParent(parent);
parent->appendNode(node); // 掛接到父節點
}
}
beginResetModel();
auto root = new TreeNode();
root->setData(nameRole, "parent 1");
root->setData(iconRole, "parenticon1");
_addEntryNode(root); // 添加根節點
auto child = new TreeNode();
child->setData(nameRole, "child1");
_addEntryNode(child, root); // 添加子節點
endResetModel();
int SqlMenuEntry::rowCount(const QModelIndex &parent) const {
if (parent.isValid()) {
TreeNode* parentNode = static_cast<TreeNode*>(parent.internalPointer());
return parentNode->childs.size(); // 返回子節點數
}
return mRootEntrys.size(); // 返回根節點數
}
QModelIndex SqlMenuEntry::index(int row, int column, const QModelIndex &parent) const {
if (!parent.isValid()) { // 根節點
if (row >= 0 && row < mRootEntrys.size())
return createIndex(row, column, mRootEntrys.at(row));
} else { // 子節點
TreeNode* parentNode = static_cast<TreeNode*>(parent.internalPointer());
if (row >= 0 && row < parentNode->childs.size())
return createIndex(row, column, parentNode->childs[row]);
}
return QModelIndex();
}
QModelIndex SqlMenuEntry::parent(const QModelIndex &child) const {
TreeNode* node = static_cast<TreeNode*>(child.internalPointer());
if (node->parent() == nullptr) return QModelIndex(); // 根節點無父
// 簡化:假設父節點在其父節點中行號為 0,列為 1
return createIndex(0, 1, node->parent());
}
QVariant SqlMenuEntry::data(const QModelIndex &index, int role) const {
TreeNode* node = static_cast<TreeNode*>(index.internalPointer());
return node->data(role);
}
TreeView {
anchors.fill: parent
TableViewColumn { title: "Name"; role: "name" }
TableViewColumn { title: "Entry"; role: "icon" }
model: MenuEntryModel {}
}
下一節將講解動態數據增刪改(insertRows, removeRows, setData)、dataChanged 信號觸發時機、以及複雜樹結構下的 parent() 高效實現(避免線性查找父節點行號)。