QTreeView 是 Qt 框架中用于展示层次化数据的核心控件,属于 Model/View 架构的重要实现。它能够以树形结构可视化复杂数据关系,并提供丰富的交互能力,是构建文件资源管理器、组织架构图、配置面板等场景的理想选择。
// 基本文件系统浏览器示例
include <QApplication>
include <QTreeView>
include <QFileSystemModel>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 使用智能指针管理资源(Chromium规则:RULE 3)
auto tree_view = std::make_unique<QTreeView>();
auto model = std::make_unique<QFileSystemModel>();
model->setRootPath(QDir::homePath());
tree_view->setModel(model.get());
tree_view->setRootIndex(model->index(QDir::homePath()));
// 现代C++特性:范围for循环设置列宽
for (int column : {0, 1, 2}) {
tree_view->setColumnWidth(column, 200);
tree_view->show();
return app.exec();
/* 架构示意图
+----------------+ +----------------+ +----------------+
| Data Model | <---> | TreeView | <---> | Delegate |
+----------------+ +----------------+ +----------------+
↓ ↑ ↑
数据存储与逻辑 显示控制与交互 渲染与编辑控制
*/
// 典型使用示例
auto model = std::make_unique<QFileSystemModel>();
auto view = std::make_unique<QTreeView>();
view->setModel(model.get()); // 建立模型与视图的绑定
// 数据流向示意图
// Model -> 通过data()提供数据 -> View通过index获取数据 -> Delegate绘制到界面
核心功能与内存布局
// 索引结构示例(伪代码)
struct QModelIndex {
int row; // 行号(从0开始)
int column; // 列号(从0开始)
void* ptr; // 内部指针(指向数据节点)
QModelIndex parent; // 父级索引
};
// 实际使用示例
void traverseModel(const QModelIndex& parent) {
const int rows = model->rowCount(parent);
const int cols = model->columnCount(parent);
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < cols; ++c) {
QModelIndex child = model->index(r, c, parent);
qDebug() << "Data:" << model->data(child);
// 递归遍历子节点
if (model->hasChildren(child)) {
traverseModel(child);
}
}
}
}
树形结构实现原理
// 自定义树节点数据结构
struct TreeNode {
QString name;
std::vector<std::unique_ptr<TreeNode>> children;
TreeNode* parent = nullptr;
explicit TreeNode(QString name, TreeNode* parent = nullptr)
: name(std::move(name)), parent(parent) {}
};
// 模型关键方法实现
QModelIndex TreeModel::parent(const QModelIndex& child) const {
if (!child.isValid()) return {};
auto* node = static_cast<TreeNode*>(child.internalPointer());
if (node->parent == rootNode) return {}; // 根节点无父项
return createIndex(findRow(node->parent), 0, node->parent);
}
int TreeModel::findRow(TreeNode* node) const {
if (!node->parent) return 0;
const auto& siblings = node->parent->children;
auto it = std::find_if(siblings.cbegin(), siblings.cend(),
[node](const auto& ptr) { return ptr.get() == node; });
return static_cast<int>(std::distance(siblings.cbegin(), it));
}
核心角色说明表
角色常量 | 值 | 数据类型 | 典型用途 |
---|---|---|---|
Qt::DisplayRole | 0 | QString | 主要显示文本 |
Qt::DecorationRole | 1 | QIcon | 条目图标 |
Qt::EditRole | 2 | QVariant | 编辑时使用的数据 |
Qt::ToolTipRole | 3 | QString | 悬浮提示信息 |
Qt::UserRole | 0x100 | 任意QVariant | 自定义扩展数据的起点 |
QVariant CustomModel::data(const QModelIndex& index, int role) const {
if (!index.isValid()) return {};
auto* node = static_cast<TreeNode*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return node->name + " (" + QString::number(node->children.size()) + ")";
case Qt::DecorationRole:
return node->isLeaf() ? QIcon(":/leaf.png") : QIcon(":/folder.png");
case Qt::ForegroundRole:
return node->modified ? QBrush(Qt::red) : QBrush(Qt::black);
case Qt::UserRole + 1:
return QVariant::fromValue(node->metadata); // 返回自定义数据
default:
return {};
}
}
// 组织架构模型示例
class OrganizationModel : public QAbstractItemModel {
Q_OBJECT
public:
// 节点数据结构
struct Node {
QString name;
std::vector<std::unique_ptr<Node>> children; // 使用智能指针
Node* parent = nullptr;
explicit Node(QString name, Node* parent = nullptr)
name(std::move(name)), parent(parent) {}
};
explicit OrganizationModel(QObject* parent = nullptr)
QAbstractItemModel(parent) {
// 初始化示例数据
root_ = std::make_unique<Node>("Root");
createTestData();
// 必须重写的接口方法
QModelIndex index(int row, int column,
const QModelIndex& parent) const override {
if (!hasIndex(row, column, parent)) return {};
Node* parentNode = getNode(parent);
Node* childNode = parentNode->children[row].get();
return createIndex(row, column, childNode);
QModelIndex parent(const QModelIndex& child) const override {
if (!child.isValid()) return {};
Node node = static_cast<Node>(child.internalPointer());
Node* parentNode = node->parent;
if (parentNode == root_.get()) return {};
return createIndex(findRow(parentNode), 0, parentNode);
int rowCount(const QModelIndex& parent) const override {
Node* node = getNode(parent);
return static_cast<int>(node->children.size());
int columnCount(const QModelIndex& /parent/) const override {
return 1;
QVariant data(const QModelIndex& index, int role) const override {
if (!index.isValid() || role != Qt::DisplayRole)
return {};
Node node = static_cast<Node>(index.internalPointer());
return node->name;
private:
std::unique_ptr<Node> root_;
Node* getNode(const QModelIndex& index) const {
if (!index.isValid()) return root_.get();
return static_cast<Node*>(index.internalPointer());
int findRow(Node* node) const {
if (!node->parent) return 0;
auto& siblings = node->parent->children;
auto it = std::find_if(siblings.begin(), siblings.end(),
const auto& ptr { return ptr.get() == node; });
return static_cast<int>(std::distance(siblings.begin(), it));
void createTestData() {
auto addDept = const QString& name {
auto node = std::make_unique<Node>(name, root_.get());
root_->children.push_back(std::move(node));
return root_->children.back().get();
};
Node* dev = addDept("Development");
dev->children.emplace_back(std::make_unique<Node>("Backend", dev));
dev->children.emplace_back(std::make_unique<Node>("Frontend", dev));
};
// 扩展OrganizationModel支持拖放
class DragDropModel : public OrganizationModel {
public:
Qt::ItemFlags flags(const QModelIndex& index) const override {
Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
return defaultFlags Qt::ItemIsDragEnabled
Qt::ItemIsDropEnabled;
QStringList mimeTypes() const override {
return {"application/vnd.treeview.item"};
QMimeData* mimeData(const QModelIndexList& indexes) const override {
auto* mimeData = new QMimeData;
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
for (const auto& index : indexes) {
if (index.isValid()) {
auto node = static_cast<Node>(index.internalPointer());
stream << reinterpret_cast<quintptr>(node);
}
mimeData->setData("application/vnd.treeview.item", data);
return mimeData;
bool dropMimeData(const QMimeData* data, Qt::DropAction action,
int row, int column, const QModelIndex& parent) override {
if (!canDropMimeData(data, action, row, column, parent))
return false;
QByteArray byteData = data->data("application/vnd.treeview.item");
QDataStream stream(&byteData, QIODevice::ReadOnly);
quintptr ptr;
stream >> ptr;
auto movedNode = reinterpret_cast<Node>(ptr);
// 实现实际的节点移动逻辑
beginMoveRows(/.../);
// ... 节点操作 ...
endMoveRows();
return true;
};
// 自定义委托示例
class CustomDelegate : public QStyledItemDelegate {
public:
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const override {
if (index.column() == 0) {
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
// 自定义绘制逻辑
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
// 绘制自定义背景
QRect rect = opt.rect;
painter->fillRect(rect, QColor(240, 240, 240));
// 绘制文本
QFont font = painter->font();
font.setBold(true);
painter->setFont(font);
painter->drawText(rect.adjusted(5, 0, 0, 0),
Qt::AlignVCenter,
index.data().toString());
painter->restore();
else {
QStyledItemDelegate::paint(painter, option, index);
}
};
// 保持业务逻辑与界面分离
class DataManager {
public:
void loadData() {
// 数据加载逻辑...
void saveData() {
// 数据保存逻辑...
};
class MyTreeView : public QTreeView {
Q_OBJECT
public:
explicit MyTreeView(DataManager manager, QWidget parent = nullptr)
QTreeView(parent), manager_(manager) {
// 初始化视图...
private:
DataManager* manager_; // 使用原始指针(遵循Chromium规则)
};
// 懒加载实现示例
QVariant BigDataModel::data(const QModelIndex& index, int role) const {
if (role == Qt::DisplayRole && !isDataLoaded(index)) {
const_cast<BigDataModel*>(this)->loadDataAsync(index);
return "Loading...";
// ...返回实际数据
// 使用QIdentityProxyModel进行数据过滤
class FilterProxyModel : public QIdentityProxyModel {
public:
void setFilter(const QString& pattern) {
beginResetModel();
filterPattern_ = pattern;
endResetModel();
protected:
bool filterAcceptsRow(int sourceRow,
const QModelIndex& sourceParent) const override {
auto index = sourceModel()->index(sourceRow, 0, sourceParent);
return index.data().toString().contains(filterPattern_);
private:
QString filterPattern_;
};
Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid));
qDebug() << "Current node:" << node->name
<< "Parent:" << (node->parent ? node->parent->name : "root");
## 性能测试代码
cpp
// 使用QBENCHMARK进行性能测试
void TreeViewTest::testExpandPerformance() {
QModelIndex root = model->index(0, 0);
QBENCHMARK {
view->expand(root);
for (int i = 0; i < model->rowCount(root); ++i) {
view->expand(model->index(i, 0, root));
}
// 典型错误场景示例
QModelIndex storedIndex = model->index(0, 0); // 存储索引
model->removeRow(0); // 删除操作后索引失效
// 错误使用方式
qDebug() << model->data(storedIndex); // 可能崩溃或返回无效数据
// 使用智能指针管理模型节点
std::vector<std::unique_ptr<Node>> children;
auto newNode = std::make_unique<Node>("New Node", parentNode);
children.push_back(std::move(newNode));
正确实践:使用QPersistentModelIndex
QPersistentModelIndex persistentIndex(model->index(0, 0));
model->removeRow(0);
if (persistentIndex.isValid()) { // 自动跟踪有效性
qDebug() << "Data:" << model->data(persistentIndex);
}
class DynamicModel : public QAbstractItemModel {
std::vector<std::unique_ptr<Node>> nodes; // 使用智能指针管理
public:
void reloadData() {
beginResetModel();
nodes.clear();
loadDataFromDatabase(); // 重新加载数据
endResetModel();
}
void partialUpdate() {
// 不恰当的重置方式 ❌
beginResetModel();
updateSomeItems();
endResetModel(); // 导致不必要的界面闪烁
}
};
操作类型 | 使用方式 | 影响范围 |
---|---|---|
全量数据更新 | beginResetModel/endResetModel | 整个模型 |
插入/删除单个项 | beginInsertRows/endInsertRows | 指定父节点的指定区域 |
修改已有项数据 | dataChanged() | 指定索引范围 |
// 安全节点结构设计
class TreeModel : public QAbstractItemModel {
struct Node {
QString id; // 唯一标识符
std::vector<std::unique_ptr<Node>> children;
Node* parent = nullptr;
};
std::unique_ptr<Node> root; // 根节点所有权明确
Node* findNodeById(const QString& id) const {
// 使用DFS/BFS查找逻辑...
}
};
// 安全索引转换方法
Node* TreeModel::getNode(const QModelIndex& index) const {
if (!index.isValid()) return root.get();
auto* node = static_cast<Node*>(index.internalPointer());
// 验证指针有效性
if (!nodeExists(node)) {
qWarning() << "Detected invalid node pointer!";
return nullptr;
}
return node;
}
bool TreeModel::nodeExists(Node* target) const {
std::function<bool(Node*)> search = [&](Node* parent) {
if (parent == target) return true;
for (auto& child : parent->children) {
if (search(child.get())) return true;
}
return false;
};
return search(root.get());
}
// 安全节点删除实现
void TreeModel::removeNode(const QModelIndex& index) {
if (!index.isValid()) return;
beginRemoveRows(index.parent(), index.row(), index.row());
Node* parentNode = getNode(index.parent());
auto& children = parentNode->children;
// 正确删除方式(Chromium规范要求使用Erase-remove惯用法)
children.erase(children.begin() + index.row());
endRemoveRows();
}
// 带缓存的模型设计
class CachedModel : public QAbstractItemModel {
struct Node {
std::string uuid; // 唯一标识
// ...其他数据成员
};
using NodePtr = std::shared_ptr<Node>;
std::unordered_map<std::string, NodePtr> nodeCache; // 中央缓存
NodePtr root; // 根节点共享指针
};
// 自定义内存跟踪器
class Node {
public:
static int instanceCount;
Node() { ++instanceCount; }
~Node() { --instanceCount; }
};
// 在模型析构时验证
TreeModel::~TreeModel() {
Q_ASSERT(Node::instanceCount == 0); // 确保所有节点已释放
}
@startuml
start
:收到数据变更请求;
if (全量更新?) then (yes)
:beginResetModel();
:重建节点结构;
:endResetModel();
else (no)
if (结构变化?) then (yes)
:beginInsert/RemoveRows;
:增删节点;
:endInsert/RemoveRows;
else
:直接调用dataChanged;
endif
endif
:更新视图显示;
stop
@enduml
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。