3. Qt基础控件(下):QComboBox、QListWidget、QTableWidget、QTreeWidget、QProgressBar
上一节我们把按钮、输入框、标签这些基础控件过了一遍。今天聊的这几个,才是真正让界面「活起来」的东西。下拉选择、列表、表格、树形结构、进度条——说白了,它们负责展示和组织数据。
我个人习惯把这五个控件分成两类:数据展示类(QComboBox、QListWidget、QTableWidget、QTreeWidget)和状态反馈类(QProgressBar)。先看数据展示,再看进度反馈。
核心观点:这五个控件是Qt数据展示的「五虎将」。掌握它们,你就能应付90%的界面数据呈现需求。
3.1 QComboBox:下拉选择框
QComboBox 说白了就是一个「节省空间的选择器」。默认只占一行,点击才展开列表。我在项目中遇到过不少新手直接往里面塞几百个选项——用户体验极差。
我的习惯:超过20个选项就用QComboBox的setEditable(true)开启可编辑模式,让用户输入过滤。或者干脆换成QListWidget。
基本用法很简单:
// 创建下拉框
QComboBox *combo = new QComboBox(this);
// 添加选项
combo->addItem("选项一");
combo->addItem("选项二");
combo->addItem("选项三");
// 也可以带图标
combo->addItem(QIcon(":/icons/file.png"), "文件");
// 获取当前选中
QString current = combo->currentText();
int index = combo->currentIndex();
// 信号监听
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
[=](int index) {
qDebug() << "选中了第" << index << "项";
});
这里有个坑——currentIndexChanged 信号有两个重载版本。老版本Qt里直接写 &QComboBox::currentIndexChanged 会报歧义。我建议用 QOverload<int>::of 或者 static_cast 明确指定。
我曾经踩过的坑:用setCurrentIndex(-1)想清空选中状态,结果在某些Qt版本上直接崩溃。安全做法是先blockSignals,再setCurrentIndex(0)并清空文本。
3.2 QListWidget:列表控件
QListWidget 适合展示一维数据列表。它比 QComboBox 更直观,所有选项都可见。
我一般用它来做文件列表、日志展示、或者简单的菜单选择。
QListWidget *list = new QListWidget(this);
// 添加项目
list->addItem("项目A");
list->addItem("项目B");
// 带图标
QListWidgetItem *item = new QListWidgetItem(QIcon(":/icons/doc.png"), "文档");
list->addItem(item);
// 设置选择模式
list->setSelectionMode(QAbstractItemView::SingleSelection);
// 其他模式:MultiSelection, ExtendedSelection
// 获取选中项
QList<QListWidgetItem*> selected = list->selectedItems();
// 双击编辑
list->setEditTriggers(QAbstractItemView::DoubleClicked);
你想想看,如果只是展示文本,QListWidget 够用了。但如果你需要自定义每行的样式——比如左边图标、中间文字、右边按钮——那就得上 QListView + 自定义 delegate。这个后面章节会讲。
| 特性 | QListWidget | QListView |
|---|---|---|
| 使用难度 | 简单,直接操作item | 需要model/delegate |
| 灵活性 | 低,样式固定 | 高,完全自定义 |
| 数据量 | 适合几百条以内 | 适合大量数据 |
| 推荐场景 | 快速原型、小项目 | 复杂界面、大项目 |
3.3 QTableWidget:表格控件
表格是展示二维数据最直观的方式。QTableWidget 是 QTableView 的便捷版,自带数据存储。
我记得第一次做项目时,用 QTableWidget 展示一个 1000 行的日志表,结果界面卡得不行。后来才明白——QTableWidget 不适合大数据量。超过几百行,建议用 QTableView + 自定义 model。
// 创建 5 行 3 列的表格
QTableWidget *table = new QTableWidget(5, 3, this);
// 设置表头
table->setHorizontalHeaderLabels({"姓名", "年龄", "城市"});
// 填充数据
table->setItem(0, 0, new QTableWidgetItem("张三"));
table->setItem(0, 1, new QTableWidgetItem("28"));
table->setItem(0, 2, new QTableWidgetItem("北京"));
// 设置列宽自适应
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
// 禁止编辑
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
// 整行选中
table->setSelectionBehavior(QAbstractItemView::SelectRows);
// 获取选中行
connect(table, &QTableWidget::itemSelectionChanged, [=]() {
int row = table->currentRow();
QString name = table->item(row, 0)->text();
});
小技巧:用 table->setAlternatingRowColors(true) 开启斑马线,视觉上舒服很多。配合 setSortingEnabled(true) 还能让用户点击表头排序。
3.4 QTreeWidget:树形控件
树形结构适合展示层级关系——文件目录、组织架构、分类导航。QTreeWidget 是 QTreeView 的便捷版。
嗯,这里要注意:树形控件的核心是「父子关系」。每个节点可以挂子节点,子节点还能继续挂。
QTreeWidget *tree = new QTreeWidget(this);
// 设置列数
tree->setColumnCount(2);
tree->setHeaderLabels({"名称", "大小"});
// 创建根节点
QTreeWidgetItem *root = new QTreeWidgetItem(tree);
root->setText(0, "项目文件");
root->setText(1, "---");
// 添加子节点
QTreeWidgetItem *child1 = new QTreeWidgetItem(root);
child1->setText(0, "main.cpp");
child1->setText(1, "2 KB");
QTreeWidgetItem *child2 = new QTreeWidgetItem(root);
child2->setText(0, "widget.ui");
child2->setText(1, "15 KB");
// 展开所有节点
tree->expandAll();
// 监听点击
connect(tree, &QTreeWidget::itemClicked, [=](QTreeWidgetItem *item, int column) {
qDebug() << "点击了:" << item->text(0);
});
我曾经在做一个文件管理器时,发现 QTreeWidget 展开收起会闪烁。后来查资料才知道——批量添加节点前要调用 setUpdatesEnabled(false),加完再恢复。这样能避免频繁重绘。
避坑指南:删除节点时,记得先判断节点是否存在。用 delete item 而不是 removeItemWidget。后者只移除显示,不释放内存。
3.5 QProgressBar:进度条
进度条是给用户「安全感」的控件。没有进度条,用户会以为程序卡死了。
基本用法:
QProgressBar *progress = new QProgressBar(this);
progress->setRange(0, 100); // 设置范围
progress->setValue(50); // 设置当前值
// 不确定进度(忙等模式)
progress->setRange(0, 0); // 范围设为 0-0 会显示忙碌动画
// 显示文本
progress->setFormat("%p%"); // 显示百分比
progress->setFormat("已处理 %v / %m 项"); // 自定义文本
实际项目中,进度条很少单独用。我一般配合 QThread 或 QtConcurrent 做后台任务更新:
// 假设在后台线程中
for (int i = 0; i < 100; ++i) {
// 处理数据...
QThread::msleep(50); // 模拟耗时操作
// 发送进度信号
emit progressUpdated(i + 1);
}
// 主线程中连接
connect(worker, &Worker::progressUpdated,
progress, &QProgressBar::setValue);
我的经验:进度条更新不要太频繁。每 1% 或每 100 次更新一次就够了。否则频繁刷新 UI 反而拖慢速度。另外,不确定进度(范围 0-0)适合用在「不知道还剩多少」的场景,比如网络请求。
3.6 综合对比与选型建议
最后给一张选型表,方便你快速决策:
| 需求场景 | 推荐控件 | 原因 |
|---|---|---|
| 单选,节省空间 | QComboBox | 默认收起,点击展开 |
| 多选,一维列表 | QListWidget | 直观,支持多选 |
| 二维表格数据 | QTableWidget | 行列清晰,自带排序 |
| 层级结构数据 | QTreeWidget | 父子节点,可展开收起 |
| 操作进度反馈 | QProgressBar | 直观显示完成度 |
| 大数据量(>1000行) | QTableView/QTreeView | model/view 架构,按需加载 |
这五个控件是 Qt 桌面开发的「基本功」。你想想看,一个文件管理器——左边 QTreeWidget 展示目录树,右边 QTableWidget 展示文件列表,底部 QProgressBar 显示复制进度。是不是全用上了?