当前位置: 首页 > news >正文

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. 性能优化

  1. 懒加载子节点:只在展开时加载子节点数据

  2. 实现 fetchMore/canFetchMore:对于大数据集

  3. 使用 uniformRowHeights:如果行高固定可提高性能

  4. 避免在 data() 中进行复杂计算:预先计算好数据

  5. 批量更新:使用 beginResetModel/endResetModel 进行大批量更新

相关文章:

  • 基于51单片机的简易示波器proteus仿真
  • 树状数组(2025钉耙编程4th 1006进步洛谷3374洛谷3368)
  • Assembly语言的装饰器
  • 【Matlab】-- 基于MATLAB的美赛常用多种算法
  • GPU中的cluster
  • 通过 Docker Swarm 集群探究 Overlay 网络跨主机通信原理
  • Windows 11 中搜索服务索引文件大处理
  • Javaweb后端 AOP快速入门 AOP核心概念 AOP执行流程
  • Springboot学习笔记 3.13
  • 若依前后端不分离字典修改---formatter对原值进行修改
  • 场外基金和ETF场内基金有何区别?ETF佣金最低是多少?
  • 从头开始学C语言第三十六天——函数指针和函数指针数组
  • 【C/C++算法】从浅到深学习---分治算法之快排思想(图文兼备 + 源码详解)
  • Html 页面图标的展示列表
  • 本地文件夹同步软件,本地文件夹同步备份方法
  • MYSQL数据库(一)
  • 六十天前端强化训练之第三十六天之E2E测试(Cypress)大师级完整指南
  • doip诊断第二版优化
  • [GWCTF 2019]我有一个数据库1 [CVE phpMyAdmin漏洞]
  • Android并发编程:线程池与协程的核心区别与最佳实践指南
  • 小说主角重生之后做网站/广州新一期lpr
  • 网站建设 手机和pc/搜索引擎推广实训
  • 快速做网站的技术/seo 最新
  • 网站前缀带wap的怎么做/网络代理app
  • 网站素材图/互联网营销课程体系
  • 公网主机上做的网站如果访问/长春seo推广