16、Qt Widgets基础(下):QComboBox、QListWidget、QTableWidget、QTreeWidget、QTabWidget
好,咱们接着聊 Qt Widgets。上一章我们把按钮、输入框、标签这些基础控件过了一遍。这一章要讲的几个控件,说实话,才是真正让你界面「活起来」的关键。它们负责展示数据、提供选择、组织信息层级。
我个人习惯把这一组控件叫做「数据展示五虎将」:下拉框、列表、表格、树、标签页。每个都有自己擅长的场景。咱们一个一个来。
核心要点:这五个控件覆盖了 90% 的数据展示需求。选对控件,你的界面就成功了一半。
16.1 QComboBox:下拉选择框
QComboBox 说白了就是一个「省地方的选择器」。你想让用户从一堆选项里挑一个,但又不想把所有选项都铺在界面上占地方——这时候就用它。
我记得刚学 Qt 那会儿,总觉得这玩意儿太简单了,不就是个下拉菜单吗?后来在项目中才发现,它的可编辑模式、自动补全功能,其实挺讲究的。
基本用法
// 创建下拉框
QComboBox *combo = new QComboBox(this);
// 添加选项
combo->addItem("苹果");
combo->addItem("香蕉");
combo->addItem("橘子");
combo->addItem("西瓜");
// 设置当前选中
combo->setCurrentIndex(0); // 默认选中第一个
// 获取当前选中
QString fruit = combo->currentText();
int index = combo->currentIndex();
小技巧:addItem() 还可以带一个 userData 参数,存一些额外数据。比如选项显示「北京」,背后存一个城市代码「010」。这样用户看到的是中文,你拿到的却是编码。
可编辑模式
有时候用户想自己输入一个不在列表里的值。嗯,这时候就需要开启可编辑模式。
combo->setEditable(true);
combo->setInsertPolicy(QComboBox::NoInsert); // 不自动插入
我在项目中遇到过一个问题:用户输入了自定义内容,但程序没做校验,结果后面处理数据时崩溃了。所以记住——可编辑模式下,一定要校验用户输入。
信号与槽
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
[=](int index) {
qDebug() << "选中了第" << index << "项";
});
注意:QComboBox 有两个 currentIndexChanged 信号,一个传 int,一个传 QString。用 QOverload 区分一下,不然编译器会懵。
16.2 QListWidget:列表控件
QListWidget 适合展示一列数据。你想想看,文件列表、聊天记录、设置选项——这些场景用列表最合适。
它比 QComboBox 更「大方」,直接把所有选项都展示出来。用户一目了然,想选哪个点哪个。
添加与操作
QListWidget *list = new QListWidget(this);
// 添加项
list->addItem("C++");
list->addItem("Qt");
list->addItem("Python");
// 插入到指定位置
list->insertItem(1, "Java");
// 删除
delete list->takeItem(2); // 删除第3项
// 获取选中
QListWidgetItem *item = list->currentItem();
if (item) {
qDebug() << item->text();
}
选择模式
默认是单选。但有时候需要多选,比如批量删除文件。
list->setSelectionMode(QAbstractItemView::MultiSelection);
其他模式还有 ExtendedSelection(按住 Ctrl 多选)、ContiguousSelection(按住 Shift 连续选)。我个人习惯用 ExtendedSelection,用户体验最好。
自定义项
列表项不只是文字。你可以加图标、复选框,甚至自定义样式。
QListWidgetItem *item = new QListWidgetItem("重要文件");
item->setIcon(QIcon(":/icons/file.png"));
item->setCheckState(Qt::Unchecked);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
list->addItem(item);
避坑指南:我曾经在项目里直接用 addItem("文本") 添加了几千条数据,界面卡得不行。后来改用 QListWidget 的批量添加方式,先准备好所有 item,再用 addItems() 一次性添加,流畅多了。
16.3 QTableWidget:表格控件
表格嘛,就是行和列。展示结构化数据最直观的方式。成绩表、订单列表、配置参数——表格通吃。
QTableWidget 是 QTableView 的简易版。它自己管理数据,不需要你额外写 model。对于中小型项目,够用了。
基本操作
QTableWidget *table = new QTableWidget(this);
// 设置行列数
table->setRowCount(5);
table->setColumnCount(3);
// 设置表头
table->setHorizontalHeaderLabels({"姓名", "年龄", "城市"});
// 填充数据
table->setItem(0, 0, new QTableWidgetItem("张三"));
table->setItem(0, 1, new QTableWidgetItem("25"));
table->setItem(0, 2, new QTableWidgetItem("北京"));
// 获取数据
QTableWidgetItem *item = table->item(0, 0);
if (item) {
qDebug() << item->text();
}
常用设置
// 整行选中
table->setSelectionBehavior(QAbstractItemView::SelectRows);
// 禁止编辑
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
// 交替行颜色
table->setAlternatingRowColors(true);
// 自适应列宽
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
经验之谈:setAlternatingRowColors(true) 这个设置,能让表格看起来舒服很多。用户看久了不累眼。我所有表格项目都会加上这一行。
排序功能
table->setSortingEnabled(true);
就一行代码,用户就能点击表头排序了。Qt 帮你做了所有脏活累活。
注意:开启排序后,如果你用 setItem 填充数据,排序会自动触发。最好在数据全部填充完后再开启排序,或者用 blockSignals 暂时屏蔽信号。
16.4 QTreeWidget:树形控件
树形结构展示层级数据,这是 QTreeWidget 的强项。文件系统、组织架构、分类目录——这些天然就是树。
说实话,树控件比列表和表格复杂一些。因为它有父子关系,操作起来要小心。
构建树
QTreeWidget *tree = new QTreeWidget(this);
// 设置列数
tree->setColumnCount(2);
tree->setHeaderLabels({"名称", "类型"});
// 创建顶层节点
QTreeWidgetItem *root = new QTreeWidgetItem(tree);
root->setText(0, "项目");
root->setText(1, "文件夹");
// 创建子节点
QTreeWidgetItem *child = new QTreeWidgetItem(root);
child->setText(0, "main.cpp");
child->setText(1, "源文件");
// 展开
tree->expandAll();
遍历与查找
// 递归遍历所有节点
void traverseTree(QTreeWidgetItem *item) {
for (int i = 0; i < item->childCount(); ++i) {
QTreeWidgetItem *child = item->child(i);
qDebug() << child->text(0);
traverseTree(child); // 递归
}
}
// 从根节点开始
for (int i = 0; i < tree->topLevelItemCount(); ++i) {
traverseTree(tree->topLevelItem(i));
}
避坑指南:我曾经在删除树节点时直接 delete item,结果程序崩溃了。正确做法是用 takeChild() 先移除,再 delete。或者直接用 removeItemWidget()。
拖拽支持
tree->setDragEnabled(true);
tree->setAcceptDrops(true);
tree->setDropIndicatorShown(true);
开启拖拽后,用户可以在树内拖动节点重新组织层级。这个功能在文件管理器类的应用中很实用。
16.5 QTabWidget:标签页控件
QTabWidget 是用来「分页」的。当界面内容太多,一屏放不下时,用标签页把它们分组。每个标签页对应一个页面,用户点击切换。
你想想看,设置对话框、多文档编辑器、浏览器标签——都是这个套路。
基本用法
QTabWidget *tab = new QTabWidget(this);
// 创建页面
QWidget *page1 = new QWidget();
QWidget *page2 = new QWidget();
QWidget *page3 = new QWidget();
// 添加标签页
tab->addTab(page1, "基本信息");
tab->addTab(page2, "高级设置");
tab->addTab(page3, "关于");
// 设置当前页
tab->setCurrentIndex(0);
常用设置
// 标签位置
tab->setTabPosition(QTabWidget::North); // 上、下、左、右
// 可关闭标签
tab->setTabsClosable(true);
// 标签可移动
tab->setMovable(true);
// 文档模式(更紧凑)
tab->setDocumentMode(true);
小技巧:setDocumentMode(true) 会让标签页看起来更现代、更简洁。我个人所有项目都会开启这个选项。
信号处理
// 标签切换
connect(tab, &QTabWidget::currentChanged, [=](int index) {
qDebug() << "切换到第" << index << "页";
});
// 标签关闭
connect(tab, &QTabWidget::tabCloseRequested, [=](int index) {
// 可以在这里询问用户是否保存
tab->removeTab(index);
});
注意:removeTab 只是移除了标签页,不会删除对应的 widget。如果你不再需要这个页面,记得手动 delete,否则会有内存泄漏。
16.6 综合示例:简易文件管理器
咱们把今天学的几个控件串起来,做一个简易文件管理器。虽然功能简单,但结构清晰,能看出每个控件的分工。
// 主窗口构造函数中
// 左侧:树形目录
QTreeWidget *tree = new QTreeWidget();
tree->setHeaderLabel("目录");
// 填充一些示例目录
QTreeWidgetItem *doc = new QTreeWidgetItem(tree, {"文档"});
new QTreeWidgetItem(doc, {"工作报告"});
new QTreeWidgetItem(doc, {"学习笔记"});
QTreeWidgetItem *img = new QTreeWidgetItem(tree, {"图片"});
new QTreeWidgetItem(img, {"2024年"});
new QTreeWidgetItem(img, {"2025年"});
// 右侧:表格展示文件列表
QTableWidget *table = new QTableWidget();
table->setColumnCount(3);
table->setHorizontalHeaderLabels({"文件名", "大小", "修改日期"});
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
// 顶部:下拉框筛选
QComboBox *filter = new QComboBox();
filter->addItems({"全部文件", "文档", "图片", "视频"});
// 用标签页组织不同视图
QTabWidget *viewTab = new QTabWidget();
viewTab->addTab(table, "列表视图");
viewTab->addTab(new QWidget(), "图标视图"); // 占位
// 布局
QSplitter *splitter = new QSplitter(Qt::Horizontal);
splitter->addWidget(tree);
splitter->addWidget(viewTab);
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(filter);
layout->addWidget(splitter);
setLayout(layout);
这个例子虽然简单,但已经涵盖了今天讲的四个控件(QTabWidget、QTreeWidget、QTableWidget、QComboBox)。每个控件各司其职,组合起来就是一个完整的界面。
总结一下:
- QComboBox:省地方,适合选项多但只选一个的场景
- QListWidget:展示列表,适合选项少且需要一目了然的场景
- QTableWidget:表格数据,适合结构化数据的展示与编辑
- QTreeWidget:树形结构,适合层级数据的组织
- QTabWidget:分页管理,适合内容多需要分组的场景
嗯,这一章的内容就到这儿。这几个控件在实际项目中几乎天天用。你写代码的时候多想想:用户需要什么?数据长什么样?选对了控件,代码写起来顺手,用户用起来也舒服。
公众号:蓝海资料掘金营,微信deep3321