3. Qt基础控件(下):QComboBox、QListWidget、QTableWidget、QTreeWidget、QProgressBar

上一节我们把按钮、输入框、标签这些基础控件过了一遍。今天聊的这几个,才是真正让界面「活起来」的东西。下拉选择、列表、表格、树形结构、进度条——说白了,它们负责展示和组织数据。

我个人习惯把这五个控件分成两类:数据展示类(QComboBox、QListWidget、QTableWidget、QTreeWidget)和状态反馈类(QProgressBar)。先看数据展示,再看进度反馈。

核心观点:这五个控件是Qt数据展示的「五虎将」。掌握它们,你就能应付90%的界面数据呈现需求。

Qt基础控件(下)知识体系 数据展示与状态反馈 数据展示控件组 QComboBox QListWidget QTableWidget QTreeWidget 状态反馈控件组 QProgressBar 使用场景 下拉选择 → 单选场景 列表/表格/树 → 多数据展示 进度条 → 操作反馈

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 显示复制进度。是不是全用上了?