Qt模型控件:QTreeViewQTreeWidget
QTreeView&QTreeWidget
- 一、QTreeView
- 1.1 核心概念:Model/View 架构
- 1.2 最简单的用法:`QStandardItemModel`
- 1. QStandardItemModel
- 核心成员函数
- `QStandardItem` 的关键函数
- 总结
- 1.3 常用功能与属性设置
- 1. 外观定制
- 2. 交互性设置
- 3. 常用操作
- 1.4 进阶用法:使用自定义模型
- 1.5 右键菜单 (Context Menu)
- 1.6 总结
- 二、QTreeWidget
- 2.1 核心概念:`QTreeWidget` vs `QTreeView`
- 2.2 基本用法
- 2.3 常用功能与属性设置
- 1. 外观定制
- 2. 交互性设置
- 3. 常用操作
- 2.4 右键菜单 (Context Menu)
- 2.5 总结:何时使用 `QTreeWidget` vs `QTreeView`
一、QTreeView
QTreeView
是 Qt 中用于显示层级数据(树状结构)的核心控件,功能非常强大和灵活。它通常与**模型(Model)**配合使用,遵循 Model/View 架构。
1.1 核心概念:Model/View 架构
在 Qt 中,QTreeView
是一个视图(View),它本身不存储任何数据。它的职责是显示由**模型(Model)**提供的数据。
- Model (模型):负责存储数据,并提供标准接口(如
rowCount
,data
,setData
)让 View 可以访问和修改数据。 - View (视图):负责将 Model 中的数据以特定的视觉形式(如树形、表格、列表)呈现给用户。
- Delegate (代理):(可选)负责数据的编辑和自定义渲染。
这种分离使得数据管理和界面显示解耦,代码更清晰、更易于维护。
1.2 最简单的用法:QStandardItemModel
对于大多数简单到中等复杂度的应用,QStandardItemModel
是首选。它是一个灵活的、可以存储 QStandardItem
对象的通用模型。
步骤:
- 创建
QTreeView
和QStandardItemModel
。 - 使用
QStandardItem
创建数据项。 - 通过
model->appendRow()
和parentItem->appendRow()
添加顶层项和子项。 - 将 Model 设置到 View 上 (
treeView->setModel(model)
)。
代码示例:
#include <QApplication>
#include <QMainWindow>
#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>int main(int argc, char *argv[])
{QApplication a(argc, argv);QMainWindow mainWindow;mainWindow.setWindowTitle("QTreeView 基础示例");// 1. 创建视图和模型QTreeView *treeView = new QTreeView(&mainWindow);QStandardItemModel *model = new QStandardItemModel(&mainWindow);model->setHeaderData(0, Qt::Horizontal, "项目"); // 设置列标题// 2. 创建根节点 (模型本身就是一个不可见的根)QStandardItem *rootItem = model->invisibleRootItem();// 3. 创建顶层项 "动物" 及其子项QStandardItem *animalItem = new QStandardItem("动物");QStandardItem *dogItem = new QStandardItem("狗");QStandardItem *catItem = new QStandardItem("猫");animalItem->appendRow(dogItem);animalItem->appendRow(catItem);rootItem->appendRow(animalItem);// 4. 创建顶层项 "植物" 及其子项QStandardItem *plantItem = new QStandardItem("植物");QStandardItem *flowerItem = new QStandardItem("花");QStandardItem *treeItem = new QStandardItem("树");plantItem->appendRow(flowerItem);plantItem->appendRow(treeItem);rootItem->appendRow(plantItem);// 5. 将模型设置到视图treeView->setModel(model);// 6. 自动展开所有节点treeView->expandAll();mainWindow.setCentralWidget(treeView);mainWindow.resize(400, 300);mainWindow.show();return a.exec();
}
1. QStandardItemModel
QStandardItemModel
是 Qt 中最常用、最灵活的模型类之一,它属于 Model/View 架构的一部分。它与 QStandardItem
配合使用,可以轻松构建各种复杂的、可编辑的数据结构,如列表、表格和树
QStandardItemModel
是一个基于项(Item)的模型。它的数据由一个二维的 QStandardItem
表格构成。
- 模型 (Model):
QStandardItemModel
本身。 - 项 (Item):
QStandardItem
对象,它是数据的基本单位,每个项可以存储多种角色(Role)的数据(如显示文本、图标、工具提示等)。 - 索引 (Index):
QModelIndex
对象,是访问模型中特定位置数据的“地址”。
核心成员函数
-
构造函数
QStandardItemModel(QObject *parent = nullptr)
: 创建一个空模型。QStandardItemModel(int rows, int columns, QObject *parent = nullptr)
: 创建一个具有指定行数和列数的空模型。
-
设置和获取数据
这些函数是QStandardItemModel
的核心,用于操作模型中的QStandardItem
。-
QStandardItem *item(int row, int column = 0) const
:- 功能: 获取指定行、列位置的
QStandardItem
指针。如果该位置没有项,则返回nullptr
。 - 示例:
QStandardItem *myItem = model->item(0, 0);
- 功能: 获取指定行、列位置的
-
void setItem(int row, int column, QStandardItem *item)
:- 功能: 在指定的行、列位置设置一个
QStandardItem
。模型会接管该 item 的所有权,你不需要手动delete
它。 - 示例:
QStandardItem *newItem = new QStandardItem("Hello"); model->setItem(0, 0, newItem);
- 功能: 在指定的行、列位置设置一个
-
QList<QStandardItem *> findItems(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly, int column = 0) const
:- 功能: 根据文本查找项。
flags
参数可以指定匹配方式(如Qt::MatchExactly
精确匹配,Qt::MatchContains
包含匹配)。 - 示例:
QList<QStandardItem*> foundItems = model->findItems("apple", Qt::MatchContains);
- 功能: 根据文本查找项。
-
-
操作行和列
这些函数用于在模型中动态地添加或删除行和列。-
bool insertRow(int row, const QList<QStandardItem *> &items)
:- 功能: 在指定行
row
插入一行。items
列表中的项将填充新行的各个列。 - 示例:
QList<QStandardItem*> newRowItems; newRowItems << new QStandardItem("New Item 1") << new QStandardItem("New Item 2"); model->insertRow(model->rowCount(), newRowItems); // 在末尾插入一行
- 功能: 在指定行
-
bool insertColumn(int column, const QList<QStandardItem *> &items)
:- 功能: 在指定列
column
插入一列。items
列表中的项将填充新列的各个行。
- 功能: 在指定列
-
void appendRow(const QList<QStandardItem *> &items)
:- 功能: 在模型末尾添加一行。这是
insertRow(model->rowCount(), items)
的便捷版本。 - 示例 (添加顶层项):
QStandardItem *topLevelItem = new QStandardItem("顶层项"); model->appendRow(topLevelItem);
- 功能: 在模型末尾添加一行。这是
-
bool removeRow(int row)
:- 功能: 删除指定的行。该行上的所有
QStandardItem
都会被自动删除。
- 功能: 删除指定的行。该行上的所有
-
bool removeColumn(int column)
:- 功能: 删除指定的列。该列上的所有
QStandardItem
都会被自动删除。
- 功能: 删除指定的列。该列上的所有
-
int rowCount(const QModelIndex &parent = QModelIndex()) const
:-
功能: 返回指定父项
parent
下的行数。对于顶层项,parent
是QModelIndex()
。 -
int columnCount(const QModelIndex &parent = QModelIndex()) const
: -
功能: 返回指定父项
parent
下的列数。
-
-
-
处理树状结构 (父项和子项)
QStandardItemModel
天生支持树状结构,通过QStandardItem
的appendRow()
方法可以轻松实现。-
QStandardItem *invisibleRootItem() const
:- 功能: 返回模型的一个不可见的根项。模型中的所有顶层项都是这个根项的子项。这在处理树状结构时非常有用,可以统一地对所有顶层项进行操作。
- 示例:
QStandardItem *root = model->invisibleRootItem(); QStandardItem *item1 = new QStandardItem("Item 1"); QStandardItem *item2 = new QStandardItem("Item 2"); root->appendRow(item1); root->appendRow(item2);
-
QModelIndex indexFromItem(const QStandardItem *item) const
:- 功能: 根据一个
QStandardItem
指针获取其对应的QModelIndex
。这在需要将项的位置传递给视图时非常有用。
- 功能: 根据一个
-
QStandardItem *itemFromIndex(const QModelIndex &index) const
:- 功能: 根据一个
QModelIndex
获取其对应的QStandardItem
指针。这是item()
函数的另一种形式。
- 功能: 根据一个
-
-
其他常用函数
-
void setHorizontalHeaderLabels(const QStringList &labels)
:- 功能: 设置水平标题栏的标签。
- 示例:
model->setHorizontalHeaderLabels(QStringList() << "Name" << "Age");
-
void clear()
:- 功能: 清除模型中的所有数据和项,将模型重置为空状态。
-
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder)
:- 功能: 根据指定的列对模型数据进行排序。
-
QStandardItem
的关键函数
QStandardItemModel
的强大之处在于 QStandardItem
,以下是 QStandardItem
的一些关键函数:
QStandardItem(const QString &text, int type = Type)
: 构造函数,创建一个带有文本的项。void setText(const QString &text)
: 设置项的显示文本。QString text() const
: 获取项的显示文本。void setData(const QVariant &value, int role = Qt::UserRole + 1)
: 设置项的特定角色(Role)的数据。角色是QStandardItem
最强大的特性之一。Qt::DisplayRole
: 显示文本 (QString
)。Qt::DecorationRole
: 图标 (QIcon
)。Qt::ToolTipRole
: 工具提示 (QString
)。Qt::UserRole
: 自定义数据的起点。- 示例:
item->setData(42, Qt::UserRole); // 存储一个自定义的整数值
QVariant data(int role = Qt::UserRole + 1) const
: 获取项的特定角色的数据。- 示例:
int value = item->data(Qt::UserRole).toInt();
- 示例:
void appendRow(QStandardItem *item)
: 向当前项添加一个子项。这是构建树状结构的核心。- 示例:
QStandardItem *parentItem = model->item(0); QStandardItem *childItem = new QStandardItem("Child"); parentItem->appendRow(childItem);
- 示例:
总结
QStandardItemModel
是一个功能强大且易于使用的模型类。
- 核心: 由
QStandardItem
对象组成的二维表格。 - 强项:
- 简单直观: API 设计友好,比直接继承
QAbstractItemModel
简单得多。 - 功能全面: 同时支持列表、表格和树状结构。
- 数据丰富: 通过
QStandardItem
的setData()
和data()
方法,可以为每个单元格存储多种角色的数据。
- 简单直观: API 设计友好,比直接继承
- 适用场景: 当你需要一个灵活的数据容器,并且数据量不是特别巨大(例如,几千到几万行以内)时,
QStandardItemModel
是你的首选。对于超大规模数据或需要高度优化性能的场景,才考虑创建自定义模型。
1.3 常用功能与属性设置
QTreeView
提供了大量函数来定制其外观和行为。
1. 外观定制
setHeaderHidden(bool hidden)
: 隐藏或显示标题栏。treeView->setHeaderHidden(true);
setIndentation(int i)
: 设置子项相对于父项的缩进像素。treeView->setIndentation(20);
setRootIsDecorated(bool show)
: 设置是否显示根节点的展开/折叠图标。对于只有一个顶层项的树,通常设为false
。treeView->setRootIsDecorated(false);
2. 交互性设置
setEditTriggers(EditTriggers triggers)
: 设置触发编辑的方式。// 设置为双击或按 F2 时可编辑 treeView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
setSelectionMode(SelectionMode mode)
: 设置选择模式(单选、多选等)。// 允许选择多个项 treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
setSelectionBehavior(SelectionBehavior behavior)
: 设置选择行为(选择整行、整列或单个单元格)。// 点击时选择整行 treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
3. 常用操作
- 获取当前选中项:
// 获取当前选中的顶层项 QModelIndex currentIndex = treeView->currentIndex(); if (currentIndex.isValid()) {QString text = currentIndex.data(Qt::DisplayRole).toString();qDebug() << "当前选中项:" << text; }
- 展开/折叠所有节点:
treeView->expandAll(); treeView->collapseAll();
- 信号与槽:
QTreeView
继承自QAbstractItemView
,提供了丰富的信号,如clicked
,doubleClicked
,pressed
,activated
等。connect(treeView, &QTreeView::clicked, [](const QModelIndex &index) {QString text = index.data(Qt::DisplayRole).toString();qDebug() << "点击了项:" << text; });
1.4 进阶用法:使用自定义模型
当你的数据结构非常复杂,或者需要更好的性能和控制时(例如,数据量巨大,或数据来自数据库),你应该创建自己的模型。
你需要继承 QAbstractItemModel
并必须重写以下纯虚函数:
rowCount(const QModelIndex &parent)
: 返回指定父项下的行数。columnCount(const QModelIndex &parent)
: 返回指定父项下的列数。data(const QModelIndex &index, int role)
: 返回指定索引和角色的数据。index(int row, int column, const QModelIndex &parent)
: 创建并返回一个索引。parent(const QModelIndex &index)
: 返回指定索引的父索引。
这部分比较复杂,但提供了无与伦比的灵活性。
1.5 右键菜单 (Context Menu)
为 QTreeView
添加右键菜单是非常常见的需求。
步骤:
- 在构造函数中设置
treeView->setContextMenuPolicy(Qt::CustomContextMenu)
。 - 连接
customContextMenuRequested(const QPoint &)
信号到一个槽函数。 - 在槽函数中,创建
QMenu
和QAction
,并使用exec()
显示菜单。
代码示例:
// 在构造函数中
treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(treeView, &QTreeView::customContextMenuRequested, this, &MainWindow::showContextMenu);// 槽函数实现
void MainWindow::showContextMenu(const QPoint &pos)
{// 将视图坐标转换为模型索引QModelIndex index = treeView->indexAt(pos);QMenu menu;QAction *addAction = menu.addAction("添加子项");QAction *deleteAction = menu.addAction("删除项");// 如果点击在无效索引上,则禁用某些操作deleteAction->setEnabled(index.isValid());QAction *selectedAction = menu.exec(treeView->viewport()->mapToGlobal(pos));if (selectedAction == addAction) {// 添加子项的逻辑...} else if (selectedAction == deleteAction) {// 删除项的逻辑...// 注意:删除操作应该通过模型来完成// model->removeRow(index.row(), index.parent());}
}
1.6 总结
QTreeView
是一个功能极其强大的控件,其核心在于与 Model 的分离。
- 入门:使用
QStandardItemModel
可以快速构建树状结构。 - 定制:通过
set...
系列函数可以轻松改变QTreeView
的外观和交互方式。 - 交互:利用信号和槽(如
clicked
,customContextMenuRequested
)响应用户操作。 - 进阶:对于复杂或高性能需求,创建自定义模型继承
QAbstractItemModel
。
二、QTreeWidget
QTreeWidget
是 QTreeView
的一个便捷子类,它将视图(View)和模型(Model)合二为一,封装了 QTreeView
和 QStandardItemModel
的组合,使得创建简单的树状结构变得更加容易。
如果你不需要自定义数据结构或复杂的模型逻辑,QTreeWidget
是一个非常好的选择。
2.1 核心概念:QTreeWidget
vs QTreeView
QTreeWidget
: “即插即用” 的树控件。它内部自带一个QStandardItemModel
,你只需要和QTreeWidgetItem
打交道,而不需要关心底层的模型接口。QTreeView
: 高度灵活的树视图。它必须与一个独立的模型(如QStandardItemModel
或自定义模型)配合使用。这种分离使得它可以处理更复杂的数据和场景。
简单来说,QTreeWidget
是为了方便而设计的,而 QTreeView
是为了灵活而设计的。
2.2 基本用法
使用 QTreeWidget
的核心是 QTreeWidgetItem
,每个 QTreeWidgetItem
代表树中的一个节点(项)。
步骤:
- 创建
QTreeWidget
控件。 - (可选)设置列数和列标题。
- 创建
QTreeWidgetItem
对象作为顶层项(根项)。 - 使用
addTopLevelItem()
将顶层项添加到QTreeWidget
。 - 创建其他
QTreeWidgetItem
对象作为子项。 - 使用
QTreeWidgetItem::addChild()
将子项添加到其父项。
代码示例:
#include <QApplication>
#include <QMainWindow>
#include <QTreeWidget>
#include <QTreeWidgetItem>int main(int argc, char *argv[])
{QApplication a(argc, argv);QMainWindow mainWindow;mainWindow.setWindowTitle("QTreeWidget 基础示例");// 1. 创建 QTreeWidget 控件QTreeWidget *treeWidget = new QTreeWidget(&mainWindow);// 2. 设置列数和列标题 (如果是多列树)// 对于单列树,这一步可以省略QStringList headers;headers << "项目" << "描述";treeWidget->setHeaderLabels(headers);// 3. 创建顶层项 "动物"QTreeWidgetItem *animalItem = new QTreeWidgetItem(QStringList() << "动物");// 4. 将顶层项添加到树控件treeWidget->addTopLevelItem(animalItem);// 5. 创建子项并添加到 "动物"QTreeWidgetItem *dogItem = new QTreeWidgetItem(QStringList() << "狗" << "忠诚的伙伴");QTreeWidgetItem *catItem = new QTreeWidgetItem(QStringList() << "猫" << "高冷的主子");animalItem->addChild(dogItem);animalItem->addChild(catItem);// 6. 创建另一个顶层项 "植物"QTreeWidgetItem *plantItem = new QTreeWidgetItem(QStringList() << "植物");treeWidget->addTopLevelItem(plantItem);// 7. 创建子项并添加到 "植物"QTreeWidgetItem *flowerItem = new QTreeWidgetItem(QStringList() << "花" << "美丽的植物");QTreeWidgetItem *treeItem = new QTreeWidgetItem(QStringList() << "树" << "高大的植物");plantItem->addChild(flowerItem);plantItem->addChild(treeItem);// 自动展开所有节点treeWidget->expandAll();mainWindow.setCentralWidget(treeWidget);mainWindow.resize(400, 300);mainWindow.show();return a.exec();
}
2.3 常用功能与属性设置
QTreeWidget
的许多方法与 QTreeView
类似,因为它继承自 QTreeView
。
1. 外观定制
setHeaderHidden(bool hidden)
: 隐藏或显示标题栏。setIndentation(int i)
: 设置子项缩进。setRootIsDecorated(bool show)
: 设置是否显示根节点的展开/折叠图标。
2. 交互性设置
setEditTriggers(EditTriggers triggers)
: 设置编辑触发方式。setSelectionMode(SelectionMode mode)
: 设置选择模式。setSelectionBehavior(SelectionBehavior behavior)
: 设置选择行为。
3. 常用操作
- 获取当前选中项:
// 获取所有选中的项 QList<QTreeWidgetItem*> selectedItems = treeWidget->selectedItems(); if (!selectedItems.isEmpty()) {QTreeWidgetItem *firstSelected = selectedItems.first();QString text = firstSelected->text(0); // 获取第0列的文本qDebug() << "当前选中项:" << text; }
- 获取当前项的父/子项:
QTreeWidgetItem *currentItem = treeWidget->currentItem(); if (currentItem) {QTreeWidgetItem *parentItem = currentItem->parent();int childCount = currentItem->childCount();// ... }
- 添加/删除项:
// 添加顶层项 treeWidget->addTopLevelItem(new QTreeWidgetItem(QStringList() << "新顶层项"));// 删除当前选中项 QTreeWidgetItem *currentItem = treeWidget->currentItem(); if (currentItem) {// 如果是顶层项if (!currentItem->parent()) {int index = treeWidget->indexOfTopLevelItem(currentItem);treeWidget->takeTopLevelItem(index);} else {// 如果是子项QTreeWidgetItem *parent = currentItem->parent();int index = parent->indexOfChild(currentItem);parent->takeChild(index);}delete currentItem; // 释放内存 }
- 信号与槽:
QTreeWidget
同样提供了丰富的信号,如itemClicked
,itemDoubleClicked
,itemPressed
等。connect(treeWidget, &QTreeWidget::itemClicked, [](QTreeWidgetItem *item, int column) {qDebug() << "点击了项:" << item->text(column) << "在列:" << column; });
2.4 右键菜单 (Context Menu)
为 QTreeWidget
添加右键菜单的方法与 QTreeView
类似。
步骤:
- 设置
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu)
。 - 连接
customContextMenuRequested(const QPoint &)
信号。 - 在槽函数中,使用
itemAt(const QPoint &)
获取鼠标位置下的项。
代码示例:
// 在构造函数中
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, &MainWindow::showContextMenu);// 槽函数实现
void MainWindow::showContextMenu(const QPoint &pos)
{// 获取鼠标位置下的项QTreeWidgetItem *item = treeWidget->itemAt(pos);QMenu menu;QAction *addAction = menu.addAction("添加子项");QAction *deleteAction = menu.addAction("删除项");// 如果没有点击任何项,则禁用操作addAction->setEnabled(item != nullptr);deleteAction->setEnabled(item != nullptr);QAction *selectedAction = menu.exec(treeWidget->viewport()->mapToGlobal(pos));if (selectedAction == addAction && item) {item->addChild(new QTreeWidgetItem(QStringList() << "新子项"));item->setExpanded(true); // 展开父项以显示新子项} else if (selectedAction == deleteAction && item) {// 删除项的逻辑...// ... (同上一节的删除代码)}
}
2.5 总结:何时使用 QTreeWidget
vs QTreeView
特性 | QTreeWidget | QTreeView |
---|---|---|
易用性 | 非常高。API 直观,开箱即用。 | 较低。需要理解 Model/View 架构,代码更复杂。 |
灵活性 | 较低。功能被封装,难以深度定制。 | 极高。可以与任何模型(包括自定义模型)配合,实现任何功能。 |
性能 | 对于大数据量可能稍逊。 | 可以通过自定义模型实现懒加载等优化,性能更好。 |
数据来源 | 主要用于内存中的静态或半静态数据。 | 可以连接到数据库、文件系统、网络等任何数据源。 |
推荐场景 | - 简单的配置树 - 文件浏览器的简化版 - 任何不需要复杂数据处理的层级列表 | - 处理海量数据 - 需要自定义数据结构 - 数据来自数据库或其他复杂源 - 需要高度定制的渲染和编辑 |
结论:
- 当你需要快速创建一个简单的、数据量不大的树状列表时,毫不犹豫地选择
QTreeWidget
。 - 当你预见到数据结构会变得复杂,或者性能是一个关键考量时,从一开始就应该使用
QTreeView
和QStandardItemModel
,这样可以避免未来重写大量代码。