QTreeWidget提供了一个树形视图来展示数据,它可以显示多级层次结构的数据
用户可以通过展开和折叠来浏览整个树,使用QTreeWidgetItem作为节点项
1. 效果演示
本案例展示表格控件的以下操作:
✅ 表头、交替显示背景色、单元格可编辑
✅ 选择行为:单元格选择或行选择
✅ 数据的增删改查

2. 属性和方法
QTableWidget有很多属性和方法,完整的可查看帮助文档。
2.1 表头
- 设置和获取列的数目、表头文本
// 获取/设置列的数目
int columnCount() const
void setColumnCount(int columns)
// 设置表头的文本
void setHeaderLabels(const QStringList &labels)
- 设置列的宽度
// 首先,获取表头
QHeaderView *header()() const
// 然后,设置列的宽度
void QHeaderView::setSectionResizeMode(QHeaderView::ResizeMode mode)
其中ResizeMode是一个枚举,取值如下:
| 枚举 | 值 | 含义 |
|---|---|---|
| HeaderView::Interactive | 0 | 用户可拖动改变列宽 |
| QHeaderView::Fixed | 2 | 固定列宽 |
| QHeaderView::Stretch | 1 | 拉伸自适应列宽大小 |
| QHeaderView::ResizeToContents | 3 | 根据内容设置列宽 |
通常,先整体设置为QHeaderView::Stretch, 然后根据需要对单独的列进行设置,如下:
// 首先,设置所有列的宽度模式为拉伸
ui->treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
// 然后,可以单独设置某一列的宽度模式
// ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
// 或者,可以单独设置某一列为固定宽度
// ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::Fixed);
// ui->treeWidget->setColumnWidth(0, 200);
2.2 Item标记
在QTreeWidget中,ItemFlags是QTreeWidgetItem项的标记,用于描述项的属性和状态,从而控制项的外观和行为
// 获取和设置单元格是否可编辑
Qt::ItemFlags flags() const;
void setFlags(Qt::ItemFlags flags);
Qt::ItemFlags是一个枚举,这些标记可以单独使用,也可以组合使用,以提供丰富的项属性控制
常用取值如下:
| 枚举 | 值 | 含义 |
|---|---|---|
| Qt::NoItemFlags | 0 | 无标记,表示项没有任何标记设置 |
| Qt::ItemIsSelectable | 1 | 项可以选择。这是默认设置,除非明确禁止 |
| Qt::ItemIsEditable | 2 | 项可以编辑 |
| Qt::ItemIsDragEnabled | 4 | 项可以拖拽。如果设置了此标记,用户可以将项拖拽到其他位置或控件中 |
| Qt::ItemIsDropEnabled | 8 | 项可以接受拖拽。如果设置了此标记,其他项可以被拖拽到此项上 |
| Qt::ItemIsUserCheckable | 16 | 该选项允许用户可以通过勾选或取消勾选来更改项目的选中状态 |
| Qt::ItemIsEnabled | 32 | 使能。项将响应用户交互(如点击、拖拽等)。默认启用 |
| Qt::ItemIsAutoTristate | 64 | 根据子项自动设置该项为三态之一 (子项全选,则该项选中,子项全不选,则该项未选中,子项部分旋转,则该项半选) |
| QT::ItemNeverHasChildren | 128 | 项永远不会有子项。如果设置了此标记,将无法向项添加子项。 |
| Qt::ItemIsUserTristate | 256 | 用户可以设置项为三态之一 |
2.3 隔行交替背景色
将奇数行和偶数行,它们的背景色不同,便于用户浏览
// 获取和设置是否允许隔行交替背景色
bool alternatingRowColors() const
void setAlternatingRowColors(bool enable)
2.4 选择模式、选择行为
2.4.1 选择行为
所谓选择行为,是指当点击一个单元格时,是选中该单元格,还是选中一整行
// 获取和设置选择行为
QAbstractItemView::SelectionBehavior selectionBehavior() const
void setSelectionBehavior(QAbstractItemView::SelectionBehavior behavior)
这是继承自其父类QAbstractItemView中的方法,QAbstractItemView::SelectionBehavior是一个枚举,取值为:
| 枚举 | 值 | 含义 |
|---|---|---|
| QAbstractItemView::SelectItems | 0 | 选中单元格 |
| QAbstractItemView::SelectRows | 1 | 选中单元格所在行 |
| QAbstractItemView::SelectColumns | 2 | 选中单元格所在列 |
2.4.2 选择模式
所谓选择模式,是指设置表格控件只可选择单行、可选择多行等
// 获取和设置选择模式
QAbstractItemView::SelectionMode selectionMode() const
void setSelectionMode(QAbstractItemView::SelectionMode mode)
这是继承自其父类QAbstractItemView中的方法,QAbstractItemView::SelectionMode是一个枚举,取值为:
| 枚举 | 值 | 含义 |
|---|---|---|
| QAbstractItemView::NoSelection | 0 | 不可选择 |
| QAbstractItemView::SingleSelection | 1 | 单行选择,一次只允许选择一行 |
| QAbstractItemView::MultiSelection | 2 | 多行选择,鼠标单击就可以选择多行 |
| QAbstractItemView::ExtendedSelection | 3 | 扩展选择,按shift键选中一个范围内的行,ctrl键可以选中不相邻的行 |
| QAbstractItemView::ContiguousSelection | 4 | 相邻选择,按shift键或ctrl键都可以选中一个范围内的行 |
2.5 增加、删除
在QTreeWidget中增加和删除项目,分两种情况:顶层项目、子项目
// 插入/追加一个顶层项
void insertTopLevelItem(int index, QTreeWidgetItem *item);
void addTopLevelItem(QTreeWidgetItem *item);
// 插入/追加多个顶层项
void insertTopLevelItems(int index, const QList<QTreeWidgetItem*> &items);
void addTopLevelItems(const QList<QTreeWidgetItem*> &items);
// 删除顶层项、删除子项
QTreeWidgetItem *takeTopLevelItem(int index);
void removeChild(QTreeWidgetItem *child);
2.6 信号槽
// 单击、双击树形控件中的项时触发。参数:item表示被点击的项,column表示被点击的列
void itemClicked(QTreeWidgetItem *item, int column)
void itemDoubleClicked(QTreeWidgetItem *item, int column)
// 展开、折叠树形控件中的项时触发。参数:item表示被展开、折叠的项。
void itemExpanded(QTreeWidgetItem *item)
void itemCollapsed(QTreeWidgetItem *item)
3. 从零实现
从零写代码实现整体效果,以演示树形控件的属性以及信号槽的用法
3.1 布局
在UI设计师界面,拖拽对应的控件,修改显示的文字、控件的名称,然后完成布局
- 将
3个GroupBox的字体的Point Size属性修改为14,并勾选Bold - 将所有其他控件的字体的
Point Size属性修改为12 - 选中
MyWidget,在右侧的属性窗口中,将Layout中的layoutStretch属性修改为8,1,也就是左右比例为8:1
布局完成效果如下:
3.2 代码实现
3.2.1 初始化表头
来到mywidget.cpp的构造函数,初始化表头,使用R字符串来设置样式表(所见即所得,更直观),如下:
MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
ui->setupUi(this);
this->setWindowTitle("明王讲QT | 第二章 常用控件 | 2.14 树形控件QTreeWidget (WX: coding4096)");
// 1. 设置表头
// 1.1 共有4列,并添加列的名称
ui->treeWidget->setColumnCount(4);
QStringList headers;
headers << "城市"
<< "面积(平方公里)"
<< "人口(万人)"
<< "GDP(亿元)";
ui->treeWidget->setHeaderLabels(headers); // 设置列标题
// 1.2 设置列的宽度
// 设置所有列的宽度模式为拉伸
ui->treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
// 可以单独设置某一列的宽度模式
// ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
// 或者,可以单独设置某一列为固定宽度
// ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::Fixed);
// ui->treeWidget->setColumnWidth(0, 200);
// 1.3 设置样式表
ui->treeWidget->setStyleSheet(R"(
QTreeView {
color:#222222;
font:16px "微软雅黑";
background-color: rgb(255, 255, 255);
alternate-background-color:rgb(250, 250, 250);
}
)");
ui->treeWidget->header()->setStyleSheet(R"(
QHeaderView::section {
color:#222222;
font:bold 16px "微软雅黑";
background-color: rgb(255, 255, 255);
border:0px solid #c8c8c8;
}
)");
}
3.2.2 初始化数据,并遍历
首先,在mywidget.h中声明初始化和遍历的成员函数,如下:
#include <QTreeWidgetItem>
class MyWidget : public QWidget {
private:
void initData();
// 遍历
int getItemDepth(QTreeWidgetItem* item); // 获取QTreeWidgetItem的深度(缩进层次)
void traverseItem(QTreeWidgetItem* item); // 递归调用,遍历当前项的所有子项
void traverseItems(QTreeWidget* treeWidget); // 遍历QTreeWidget中的所有顶级项
};
然后,在mywidget.cpp中实现初始化函数,如下:
void MyWidget::initData() {
// 1. 添加顶级item-北京
QTreeWidgetItem* beiJing = new QTreeWidgetItem({"北京市", "16807", "2186", "43800"});
ui->treeWidget->addTopLevelItem(beiJing);
// 2. 添加顶级item-上海
QTreeWidgetItem* shangHai = new QTreeWidgetItem({"上海市", "6340", "2487", "47200"});
ui->treeWidget->addTopLevelItem(shangHai);
// 3. 添加顶级item-广东省
QTreeWidgetItem* guangDong = new QTreeWidgetItem({"广东省"});
ui->treeWidget->addTopLevelItem(guangDong);
QTreeWidgetItem* guangZhou = new QTreeWidgetItem({"广州市", "7434", "1882", "30355"});
QTreeWidgetItem* shenZhen = new QTreeWidgetItem({"深圳市", "1997", "1779", "34606"});
guangDong->addChild(guangZhou);
guangDong->addChild(shenZhen);
// 4. 添加顶级item-浙江省
QTreeWidgetItem* zheJiang = new QTreeWidgetItem({"浙江省"});
ui->treeWidget->addTopLevelItem(zheJiang);
QTreeWidgetItem* hangZhou = new QTreeWidgetItem({"杭州市", "16850", "1252", "20059"});
QTreeWidgetItem* ningBo = new QTreeWidgetItem({"宁波市", "9816", "969", "16452"});
QTreeWidgetItem* wenZhou = new QTreeWidgetItem({"温州市", "11784", "976", "8730"});
zheJiang->addChild(hangZhou);
zheJiang->addChild(ningBo);
zheJiang->insertChild(1, wenZhou);
// 5. 插入顶级item-天津
QTreeWidgetItem* tianJin = new QTreeWidgetItem({"天津", "11966", "1363", "16737"});
ui->treeWidget->insertTopLevelItem(2, tianJin); // 2 表示插入到第2个位置
// 6. 添加顶级item-江苏
QTreeWidgetItem* jiangSu = new QTreeWidgetItem({"江苏"});
ui->treeWidget->addTopLevelItem(jiangSu);
QTreeWidgetItem* suZhou = new QTreeWidgetItem({"苏州市", "8657", "1295", "24653"});
QTreeWidgetItem* nanJing = new QTreeWidgetItem({"南京市", "6587", "954", "17421"});
QTreeWidgetItem* wuXi = new QTreeWidgetItem({"无锡市", "4627", "749", "15456"});
jiangSu->addChild(suZhou);
jiangSu->addChild(nanJing);
jiangSu->insertChild(1, wuXi);
}
然后,在mywidget.cpp中实现遍历函数,如下:
// 获取QTreeWidgetItem的深度(缩进层次)
int MyWidget::getItemDepth(QTreeWidgetItem* item) {
// 如果条目为空,则深度为0
if ( !item ) {
return 0;
}
// 如果条目没有父项,则为根条目,深度为1
if ( !item->parent() ) {
return 1;
}
// 递归地获取父项的深度,并加1
return getItemDepth(item->parent()) + 1;
}
// 递归调用,遍历当前项的所有子项
void MyWidget::traverseItem(QTreeWidgetItem* item) {
// 如果项为空,则返回
if ( !item ) {
return;
}
// 首先,根据层次深度,添加打印时的缩进
QString s = "";
int depth = getItemDepth(item);
if ( depth == 1 ) {
} else if ( depth == 2 ) {
s += QString(" ").repeated(4);
} else if ( depth == 3 ) {
s += QString(" ").repeated(8);
} else if ( depth == 4 ) {
s += QString(" ").repeated(12);
}
// 然后,追加每一列
int childCount = item->childCount();
if ( childCount == 0 ) {
s += (item->text(0) + "|" + item->text(1) + "|" + item->text(2) + "|" + item->text(3));
} else {
s += item->text(0);
}
// qDebug() << s;
qDebug().noquote() << s;
for ( int i = 0; i < childCount; ++i ) {
QTreeWidgetItem* childItem = item->child(i);
traverseItem(childItem);
}
}
// 遍历QTreeWidget中的所有顶级项
void MyWidget::traverseItems(QTreeWidget* treeWidget) {
// 遍历所有顶级项
int topLevelItemCount = treeWidget->topLevelItemCount();
for ( int i = 0; i < topLevelItemCount; ++i ) {
QTreeWidgetItem* topLevelItem = treeWidget->topLevelItem(i);
traverseItem(topLevelItem); // 对每个顶级项调用递归函数
}
}
最后,在mywidget.cpp的构造中,调用初始化数据和遍历函数,如下:
MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
// 2. 初始化数据,并遍历所有的节点,也就是QTreeWidgetItem
initData();
traverseItems(ui->treeWidget);
}
此时,控制台打印效果:
3.2.3 复选按钮槽函数
首先,在mywidget.h中声明3个槽函数,以及2个成员函数,如下:
class Widget : public QWidget {
private:
// 设置条目是否可编辑
void setItemEditable(QTreeWidgetItem* item, bool editable); // 递归调用,遍历当前项的所有子项
void setItemsEditable(QTreeWidget* treeWidget, bool editable); // 遍历QTreeWidget中的所有顶级项
private slots:
void on_chkHeader_checkStateChanged(Qt::CheckState state);
void on_chkAlternating_checkStateChanged(Qt::CheckState state);
void on_chkCellEditable_checkStateChanged(Qt::CheckState state);
};
然后,在mywidget.cpp中实现这5个函数,如下:
// 递归调用,遍历当前项的所有子项
void MyWidget::setItemEditable(QTreeWidgetItem* item, bool editable) {
// 如果项为空,则返回
if ( !item ) {
return;
}
// 设置项为可编辑
// qDebug() << "before: " << item->flags();
if ( editable ) {
item->setFlags(item->flags() | Qt::ItemIsEditable);
} else {
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
}
qDebug() << "after: " << item->flags();
for ( int i = 0; i < item->childCount(); ++i ) {
QTreeWidgetItem* childItem = item->child(i);
setItemEditable(childItem, editable);
}
}
// 遍历QTreeWidget中的所有顶级项
void MyWidget::setItemsEditable(QTreeWidget* treeWidget, bool editable) {
// 遍历所有顶级项
int topLevelItemCount = treeWidget->topLevelItemCount();
for ( int i = 0; i < topLevelItemCount; ++i ) {
QTreeWidgetItem* topLevelItem = treeWidget->topLevelItem(i);
setItemEditable(topLevelItem, editable); // 对每个顶级项调用递归函数
}
}
void MyWidget::on_chkHeader_checkStateChanged(Qt::CheckState state) {
if ( state == Qt::Checked ) {
ui->treeWidget->header()->show();
} else if ( state == Qt::Unchecked ) {
ui->treeWidget->header()->hide();
}
}
void MyWidget::on_chkAlternating_checkStateChanged(Qt::CheckState state) {
if ( state == Qt::Checked ) {
ui->treeWidget->setAlternatingRowColors(true);
} else if ( state == Qt::Unchecked ) {
ui->treeWidget->setAlternatingRowColors(false);
}
}
void MyWidget::on_chkCellEditable_checkStateChanged(Qt::CheckState state) {
if ( state == Qt::Checked ) {
// 当双击单元格/选中单元格然后单击/按下编辑键F2,都可以编辑单元格。
// QTreeWidget本身没有直接设置所有项为可编辑的接口。
// ui->treeWidget->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed);
// 这里遍历所有的item,将它们逐一设置为可编辑(更简单地,通常先将item设置为可编辑,再添加到treeWidget中)
setItemsEditable(ui->treeWidget, true);
} else if ( state == Qt::Unchecked ) {
// ui->treeWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
setItemsEditable(ui->treeWidget, false);
}
}
最后,在mywidget.cpp的构造函数中,默认勾选上 “表头” 复选框,如下:
MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
// 3. 默认显示表头
ui->chkHeader->click();
}
3.2.4 行选择、单元格选择
首先,在mywidget.h中声明槽函数,如下:
#include <QButtonGroup>
class MyWidget : public QWidget {
private slots:
void onBtnGroupClicked(int id);
private:
QButtonGroup* btnGroup;
};
然后,在mywidget.cpp中实现这个槽函数,如下:
void MyWidget::onBtnGroupClicked(int id) {
ui->treeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
ui->treeWidget->setSelectionBehavior(id == 0 ? QAbstractItemView::SelectItems : QAbstractItemView::SelectRows);
}
最后,在mywidget.cpp的构造函数中,关联信号槽,并将两个单选按钮放到一个组内,如下:
MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
// 4. 设置行选择还是单元格选择
btnGroup = new QButtonGroup(this);
btnGroup->addButton(ui->radioCellSelect, 0);
btnGroup->addButton(ui->radioRowSelect, 1);
connect(btnGroup, &QButtonGroup::idClicked, this, &MyWidget::onBtnGroupClicked);
ui->radioRowSelect->setChecked(true);
}
3.2.5 增删改查
首先,在mywidget.h中声明槽函数,如下:
class MyWidget : public QWidget {
private slots:
void on_treeWidget_itemClicked(QTreeWidgetItem* item, int columnitem); // 条目点击
void on_btnAppendSibling_clicked(); // 追加同级
void on_btnInsertSibling_clicked(); // 插入同级
void on_btnAddChild_clicked(); // 添加子节点
void on_btnModify_clicked(); // 修改
void on_btnDelete_clicked(); // 删除
};
然后,在mywidget.cpp中实现这几个槽函数,如下:
void MyWidget::on_treeWidget_itemClicked(QTreeWidgetItem* item, int columnitem) {
ui->lineEditCity->setText(item->text(0));
ui->lineEditArea->setText(item->text(1));
ui->lineEditPopulation->setText(item->text(2));
ui->lineEditGDP->setText(item->text(3));
}
// 在当前条目同级的最后追加
void MyWidget::on_btnAppendSibling_clicked() {
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if ( currentItem ) {
QString city = ui->lineEditCity->text().trimmed();
QString area = ui->lineEditArea->text().trimmed();
QString population = ui->lineEditPopulation->text().trimmed();
QString gdp = ui->lineEditGDP->text().trimmed();
QTreeWidgetItem* item = new QTreeWidgetItem({city, area, population, gdp});
QTreeWidgetItem* parent = currentItem->parent();
if ( parent ) {
parent->addChild(item);
} else {
ui->treeWidget->addTopLevelItem(item);
}
}
}
// 在当前条目之前插入
void MyWidget::on_btnInsertSibling_clicked() {
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if ( currentItem ) {
QString city = ui->lineEditCity->text().trimmed();
QString area = ui->lineEditArea->text().trimmed();
QString population = ui->lineEditPopulation->text().trimmed();
QString gdp = ui->lineEditGDP->text().trimmed();
QTreeWidgetItem* item = new QTreeWidgetItem({city, area, population, gdp});
QTreeWidgetItem* parent = currentItem->parent();
if ( parent ) {
int index = parent->indexOfChild(currentItem); // 获取该节点在父节点中的索引
parent->insertChild(index, item);
} else {
int index = ui->treeWidget->indexOfTopLevelItem(currentItem);
ui->treeWidget->insertTopLevelItem(index, item);
}
}
}
// 为当前条目添加子节点
void MyWidget::on_btnAddChild_clicked() {
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if ( currentItem ) {
QString city = ui->lineEditCity->text().trimmed();
QString area = ui->lineEditArea->text().trimmed();
QString population = ui->lineEditPopulation->text().trimmed();
QString gdp = ui->lineEditGDP->text().trimmed();
QTreeWidgetItem* item = new QTreeWidgetItem({city, area, population, gdp});
currentItem->addChild(item);
}
}
// 修改选中的条目
void MyWidget::on_btnModify_clicked() {
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if ( currentItem ) {
currentItem->setText(0, ui->lineEditCity->text().trimmed());
currentItem->setText(1, ui->lineEditArea->text().trimmed());
currentItem->setText(2, ui->lineEditPopulation->text().trimmed());
currentItem->setText(3, ui->lineEditGDP->text().trimmed());
}
}
// 删除当前条目
void MyWidget::on_btnDelete_clicked() {
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if ( currentItem ) {
QTreeWidgetItem* parent = currentItem->parent();
if ( parent ) {
parent->removeChild(currentItem);
delete currentItem;
} else {
int index = ui->treeWidget->indexOfTopLevelItem(currentItem);
QTreeWidgetItem* item = ui->treeWidget->takeTopLevelItem(index);
delete item;
}
}
}
4. 点赞、获取源码
看到这里的小伙伴,去B站给明王一个【免费的点赞】吧,你的支持,是我持续更新优质内容的动力,感谢~
源码下载地址
链接: https://pan.baidu.com/s/1-Bb-VFhb5208LLkoZtrTMQ
提取码: ming







