QTreeView开发入门
1. QTreeView 基础介绍
QTreeView 是 Qt 框架中用于显示树形结构数据的控件,属于模型/视图架构的一部分。它非常适合展示层次化数据,如文件系统、组织结构等。
主要特点:
-
支持多级层次结构显示
-
可展开/折叠节点
-
支持自定义节点样式
-
提供选择、编辑功能
-
可与 QFileSystemModel 等现成模型配合使用
2. 基本使用
2.1 简单示例
#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 创建模型
QStandardItemModel model;
// 添加数据
QStandardItem *rootItem = model.invisibleRootItem();
// 第一级节点
QStandardItem *item1 = new QStandardItem("一级节点1");
QStandardItem *item2 = new QStandardItem("一级节点2");
rootItem->appendRow(item1);
rootItem->appendRow(item2);
// 第二级节点
QStandardItem *item11 = new QStandardItem("二级节点1-1");
QStandardItem *item12 = new QStandardItem("二级节点1-2");
item1->appendRow(item11);
item1->appendRow(item12);
QStandardItem *item21 = new QStandardItem("二级节点2-1");
item2->appendRow(item21);
// 创建TreeView
QTreeView treeView;
treeView.setModel(&model);
treeView.setWindowTitle("树形视图示例");
treeView.expandAll(); // 展开所有节点
treeView.resize(400, 300);
treeView.show();
return app.exec();
}
2.2 常用方法
// 设置选择模式
treeView.setSelectionMode(QAbstractItemView::SingleSelection); // 单选
treeView.setSelectionMode(QAbstractItemView::MultiSelection); // 多选
// 设置选择行为
treeView.setSelectionBehavior(QAbstractItemView::SelectRows); // 整行选择
// 设置是否可编辑
treeView.setEditTriggers(QAbstractItemView::NoEditTriggers); // 不可编辑
treeView.setEditTriggers(QAbstractItemView::DoubleClicked); // 双击编辑
// 设置展开/折叠动画
treeView.setAnimated(true);
// 设置根节点是否显示
treeView.setRootIsDecorated(true); // 显示根节点装饰
// 设置列数
treeView.setHeaderHidden(true); // 隐藏表头
3. 数据模型使用
3.1 使用 QStandardItemModel
QStandardItemModel *model = new QStandardItemModel(this);
// 设置表头(多列情况)
model->setHorizontalHeaderLabels({"名称", "类型", "大小"});
// 创建节点
QStandardItem *rootNode = model->invisibleRootItem();
// 添加一级节点
QStandardItem *folderItem = new QStandardItem(QIcon(":/icons/folder.png"), "文件夹");
QStandardItem *typeItem = new QStandardItem("文件夹");
QStandardItem *sizeItem = new QStandardItem("-");
rootNode->appendRow({folderItem, typeItem, sizeItem});
// 添加子节点
QStandardItem *fileItem = new QStandardItem(QIcon(":/icons/file.png"), "文档.txt");
QStandardItem *fileType = new QStandardItem("文本文件");
QStandardItem *fileSize = new QStandardItem("12 KB");
folderItem->appendRow({fileItem, fileType, fileSize});
ui->treeView->setModel(model);
ui->treeView->setColumnWidth(0, 200); // 设置第一列宽度
3.2 使用 QFileSystemModel
QFileSystemModel *fileModel = new QFileSystemModel(this);
fileModel->setRootPath(QDir::homePath()); // 设置根路径
// 设置过滤器
fileModel->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
fileModel->setNameFilters({"*.txt", "*.pdf"});
fileModel->setNameFilterDisables(false);
ui->treeView->setModel(fileModel);
ui->treeView->setRootIndex(fileModel->index(QDir::homePath()));
ui->treeView->hideColumn(1); // 隐藏不需要的列
ui->treeView->hideColumn(2);
ui->treeView->hideColumn(3);
4. 自定义模型
继承 QAbstractItemModel 创建自定义树模型:
class TreeModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit TreeModel(QObject *parent = nullptr);
// 必须实现的方法
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
// 可选实现的方法
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
private:
struct TreeNode {
QString name;
QVector<TreeNode*> children;
TreeNode *parent = nullptr;
};
TreeNode *rootNode;
};
4.1 关键方法实现
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const {
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeNode *parentNode = parent.isValid() ?
static_cast<TreeNode*>(parent.internalPointer()) :
rootNode;
TreeNode *childNode = parentNode->children.value(row, nullptr);
return childNode ? createIndex(row, column, childNode) : QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &child) const {
if (!child.isValid())
return QModelIndex();
TreeNode *childNode = static_cast<TreeNode*>(child.internalPointer());
TreeNode *parentNode = childNode->parent;
if (parentNode == rootNode)
return QModelIndex();
// 查找父节点在其父级的行号
TreeNode *grandParentNode = parentNode->parent;
int row = grandParentNode->children.indexOf(parentNode);
return createIndex(row, 0, parentNode);
}
int TreeModel::rowCount(const QModelIndex &parent) const {
if (parent.column() > 0)
return 0;
TreeNode *parentNode = parent.isValid() ?
static_cast<TreeNode*>(parent.internalPointer()) :
rootNode;
return parentNode->children.size();
}
QVariant TreeModel::data(const QModelIndex &index, int role) const {
if (!index.isValid())
return QVariant();
TreeNode *node = static_cast<TreeNode*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return node->name;
case Qt::DecorationRole:
return QIcon(":/icons/node.png");
case Qt::ToolTipRole:
return QString("节点: %1").arg(node->name);
default:
return QVariant();
}
}
5. 高级功能实现
5.1 自定义委托
class TreeItemDelegate : public QStyledItemDelegate {
public:
using QStyledItemDelegate::QStyledItemDelegate;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
// 自定义绘制
painter->save();
// 设置背景
if (opt.state & QStyle::State_Selected) {
painter->fillRect(opt.rect, QColor("#4CAF50"));
} else if (opt.state & QStyle::State_MouseOver) {
painter->fillRect(opt.rect, QColor("#E8F5E9"));
} else {
painter->fillRect(opt.rect, Qt::white);
}
// 绘制图标
QRect iconRect = opt.rect.adjusted(2, 2, -opt.rect.width() + 30, -2);
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
icon.paint(painter, iconRect);
// 绘制文本
QRect textRect = opt.rect.adjusted(32, 0, -10, 0);
painter->setPen(opt.state & QStyle::State_Selected ? Qt::white : Qt::black);
painter->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, opt.text);
painter->restore();
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override {
return QSize(200, 30); // 固定行高
}
};
// 使用自定义委托
ui->treeView->setItemDelegate(new TreeItemDelegate(this));
5.2 拖放功能
// 启用拖放
ui->treeView->setDragEnabled(true);
ui->treeView->setAcceptDrops(true);
ui->treeView->setDropIndicatorShown(true);
ui->treeView->setDragDropMode(QAbstractItemView::InternalMove);
// 在模型中实现拖放支持
Qt::DropActions TreeModel::supportedDropActions() const {
return Qt::CopyAction | Qt::MoveAction;
}
QStringList TreeModel::mimeTypes() const {
return {"application/vnd.tree.item"};
}
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const {
auto *mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
TreeNode *node = static_cast<TreeNode*>(index.internalPointer());
stream << node->name;
}
}
mimeData->setData("application/vnd.tree.item", encodedData);
return mimeData;
}
bool TreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) {
if (!data->hasFormat("application/vnd.tree.item"))
return false;
QByteArray encodedData = data->data("application/vnd.tree.item");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
}
TreeNode *parentNode = parent.isValid() ?
static_cast<TreeNode*>(parent.internalPointer()) :
rootNode;
beginInsertRows(parent, row, row + newItems.size() - 1);
for (const QString &text : qAsConst(newItems)) {
TreeNode *newNode = new TreeNode{text, {}, parentNode};
parentNode->children.insert(row++, newNode);
}
endInsertRows();
return true;
}
6. 信号与槽
// 当前项变化信号
connect(ui->treeView, &QTreeView::clicked, [](const QModelIndex &index){
qDebug() << "点击了:" << index.data().toString();
});
// 双击项目信号
connect(ui->treeView, &QTreeView::doubleClicked, [](const QModelIndex &index){
qDebug() << "双击了:" << index.data().toString();
});
// 展开/折叠信号
connect(ui->treeView, &QTreeView::expanded, [](const QModelIndex &index){
qDebug() << "展开了:" << index.data().toString();
});
connect(ui->treeView, &QTreeView::collapsed, [](const QModelIndex &index){
qDebug() << "折叠了:" << index.data().toString();
});
// 选择变化信号
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
[](const QItemSelection &selected, const QItemSelection &deselected){
qDebug() << "选择已改变";
});
7. 性能优化
-
懒加载子节点:只在展开时加载子节点数据
-
实现 fetchMore/canFetchMore:对于大数据集
-
使用 uniformRowHeights:如果行高固定可提高性能
-
避免在 data() 中进行复杂计算:预先计算好数据
-
批量更新:使用 beginResetModel/endResetModel 进行大批量更新