1.前言
在现代GUI开发中,实时反馈和数据可视化是提升用户体验的核心要素。无论是桌面应用、工业控制还是嵌入式系统,开发者常面临这样的需求:
- 需要清晰告知用户耗时操作的进度
- 文件传输、数据导入、算法计算等场景中,用户渴望获得明确的进度预期
- 糟糕的实现:界面卡死,用户误以为程序崩溃
- 优雅的方案:
QProgressBar动态可视化进程状态
- 需要高效展示和编辑结构化数据
- 从简单的配置表格到复杂的数据看板,二维数据展示无处不在
- 原生控件的痛点:手动实现排序、编辑、样式调整成本高昂
- Qt的答案:
QTableWidget提供开箱即用的交互式表格功能
本文将讲解这两个核心控件。
2.QProgressBar
QProgressBar 是 Qt 提供的专门用于进度显示的控件,具有简洁明了的界面表现力。
2.1基础用法
QProgressBar *progressBar = new QProgressBar(this);
progressBar->setRange(0, 100); // 设置进度范围
progressBar->setValue(30); // 当前值为30%
progressBar->setTextVisible(true); // 显示百分比文字
progressBar->setAlignment(Qt::AlignCenter); // 文本居中
2.2 常用方法
| 方法 / 属性 | 说明 |
|---|---|
setRange(min, max) | 设置进度范围 |
setValue(value) | 设置当前进度 |
setTextVisible(bool) | 显示或隐藏文字(如“30%”) |
setFormat("已完成 %p%") | 自定义文本显示,%p 表示百分比 |
setOrientation(Qt::Horizontal/Vertical) | 设置水平或垂直方向 |
setInvertedAppearance(true) | 反转进度条显示方向 |
setRange(0, 0) | 启用“不确定”状态,显示为循环滚动条 |
reset() | 重置进度条为最小值 |
2.3动态样式
基础QSS美化
progressBar->setStyleSheet(
"QProgressBar {"
" border: 2px solid #3A3A3A;"
" border-radius: 5px;"
" text-align: center;"
" background: #F0F0F0;"
"}"
"QProgressBar::chunk {"
" background: QLinearGradient(x1:0,y1:0,x2:1,y2:0,"
" stop:0 #FF8000, stop:1 #FF0000);" // 橙色到红色渐变
" border-radius: 3px;"
"}"
);
动态颜色变化(根据进度值)
// 在valueChanged信号中动态修改样式
connect(progressBar, &QProgressBar::valueChanged, [=](int value) {
QString color;
if (value < 30) color = "#FF0000"; // 红色
else if (value < 70) color = "#FFC000"; // 黄色
else color = "#00FF00"; // 绿色
progressBar->setStyleSheet(
QString("QProgressBar::chunk { background: %1; }").arg(color));
});
2.4 示例:文件下载模拟
QProgressBarDemo.h
#include <QtWidgets/QWidget>
#include<qprogressbar.h>
#include<QVBoxLayout>
#include<QHBoxLayout>
#include<qpushbutton.h>
#include<qmessagebox.h>
#include<qtimer.h>
#include<random>
class QProgressBarDemo : public QWidget
{
Q_OBJECT
public:
QProgressBarDemo(QWidget *parent = nullptr);
~QProgressBarDemo();
private:
QProgressBar* progress;
QPushButton* btnStart;
QPushButton* btnCancel;
QTimer* timer;
std::mt19937 randomEngine;
std::uniform_int_distribution<> dist;
//更新进度条样式
void updateProgressStyle(int value);
void startDownload();
void cancelDownload();
};
QProgressBarDemo.cpp
#include "QProgressBarDemo.h"
QProgressBarDemo::QProgressBarDemo(QWidget *parent)
: QWidget(parent)
{
setWindowTitle("文件下载模拟");
setFixedSize(300, 200);
QVBoxLayout* mainLayout = new QVBoxLayout(this);
//进度条设置
progress = new QProgressBar;
progress->setRange(0, 100);
progress->setAlignment(Qt::AlignCenter);
progress->setFormat("已下载:%p%");
updateProgressStyle(0); //初始样式
//按钮
QHBoxLayout* btnLayout = new QHBoxLayout();
btnStart = new QPushButton("开始下载");
btnCancel = new QPushButton("取消");
btnCancel->setEnabled(false);
btnLayout->addWidget(btnStart);
btnLayout->addWidget(btnCancel);
//随机数生成器初始化
randomEngine = std::mt19937(std::random_device{}());
dist = std::uniform_int_distribution<>(1, 5);
//信号连接
connect(btnStart, &QPushButton::clicked, this, &QProgressBarDemo::startDownload);
connect(btnCancel, &QPushButton::clicked, this, &QProgressBarDemo::cancelDownload);
mainLayout->addWidget(progress);
mainLayout->addLayout(btnLayout);
}
QProgressBarDemo::~QProgressBarDemo()
{}
void QProgressBarDemo::updateProgressStyle(int value)
{
//如果当前显示“已取消”,不再更新样式
if (progress->format().contains("已取消"))return;
QString color;
if (value < 30) color = "#FF5555"; //红色
else if (value < 70) color = "#FFC000"; //黄色
else color = "#55FF55"; //绿色
progress->setStyleSheet(QString(
"QProgressBar {"
" border: 2px solid grey;"
" border-radius: 5px;"
" text-align: center;"
" background: #F0F0F0;"
"}"
"QProgressBar::chunk {"
" background: %1;"
" border-radius: 3px;"
" width: 10px;" // 块状效果
"}"
"QProgressBar {"
" font-weight: bold;"
" color: #333333;"
"}"
).arg(color));
//动态文本
static int anim = 0;
QString animStr = QString("下载中%1").arg(QString(anim % 3 + 1, '.'));
progress->setFormat(QString("%1 %2 %").arg(animStr).arg(value));
anim++;
}
void QProgressBarDemo::startDownload()
{
progress->setFormat("已下载:%p");
btnStart->setEnabled(false);
btnCancel->setEnabled(true);
progress->setValue(0);
timer = new QTimer(this);
connect(timer, &QTimer::timeout, [this]() {
int newValue = progress->value() + dist(randomEngine); //随机增量
progress->setValue(newValue);
updateProgressStyle(newValue);
if (newValue >= 100) {
newValue = 100;
timer->stop();
btnStart->setEnabled(true);
btnCancel->setEnabled(false);
progress->setFormat("下载完成!");
QMessageBox::information(this, "下载提示", "下载完成");
}
});
timer->start(200); //每200ms更新
}
void QProgressBarDemo::cancelDownload()
{
if (timer)timer->stop();
progress->blockSignals(true);
progress->setFormat("已取消");
progress->setValue(0);
progress->blockSignals(false);
btnStart->setEnabled(true);
btnCancel->setEnabled(false);
updateProgressStyle(0);
}
效果演示
界面初始效果
点击“开始下载”
下载完成
点击“取消”
3.QTableWidget
QTableWidget 是 Qt 中用于展示表格数据的控件,继承自 QTableView,但使用上更简单、直接,适合静态或中小规模的数据展示。其每个单元格都是 QTableWidgetItem,也可以自定义控件嵌入单元格。
3.1基础用法
初始化表格
QTableWidget* table = new QTableWidget(this);
table->setRowCount(3); // 设置行数
table->setColumnCount(2); // 设置列数
QStringList headers = { "姓名", "年龄" };
table->setHorizontalHeaderLabels(headers); // 设置表头
设置单元格内容
table->setItem(0, 0, new QTableWidgetItem("张三"));
table->item(0, 0)->setTextAlignment(Qt::AlignCenter);
table->setItem(0, 1, new QTableWidgetItem("18"));
3.2 行为设置
table->setEditTriggers(QAbstractItemView::NoEditTriggers); // 禁止编辑
table->setSelectionBehavior(QAbstractItemView::SelectRows); // 选择整行
table->horizontalHeader()->setStretchLastSection(true); // 最后一列自动拉伸
table->verticalHeader()->setVisible(false); // 隐藏行号
3.3 自定义内容
嵌入控件
// 添加按钮
QPushButton *btn = new QPushButton("编辑");
table->setCellWidget(0, 4, btn);
connect(btn, &QPushButton::clicked, []{ /* 处理逻辑 */ });
// 添加复选框
QCheckBox *check = new QCheckBox();
check->setChecked(true);
table->setCellWidget(1, 3, check);
右键菜单
table->setContextMenuPolicy(Qt::CustomContextMenu);
connect(table, &QTableWidget::customContextMenuRequested, [=](const QPoint &pos){
QMenu menu;
menu.addAction("删除行", [=]{
table->removeRow(table->rowAt(pos.y()));
});
menu.exec(table->viewport()->mapToGlobal(pos));
});
条件格式化
for(int row=0; row<table->rowCount(); row++) {
if(table->item(row, 2)->text().toInt() > 100) {
table->item(row, 2)->setBackground(Qt::yellow);
}
}
3.4 数据读取
QTableWidgetItem* item = table->item(0, 0);
if (item) {
qDebug() << "单元格内容:" << item->text();
}
3.5 信号与槽
connect(table, &QTableWidget::cellClicked, [](int row, int col){
qDebug() << "点击了第" << row << "行, 第" << col << "列";
});
4.综合案例:表格数据管理系统
功能说明:
| 控件 | 功能实现 |
|---|---|
| QRadioButton | 性别筛选(男/女) |
| QCheckBox | 可扩展为多选筛选条件 |
| QSpinBox | 设置年龄下限筛选 |
| QComboBox | 部门选择(表格内嵌+筛选条件) |
| QPushButton | 触发添加/删除/保存操作 |
| QProgressBar | 模拟数据保存进度 |
| QTableWidget | 支持: 动态增删行、 混合文本和控件单元格 、条件筛选 、 样式控制 |
代码示例:
QTableWidgetDemo.h
#pragma once
#include <QtWidgets/QWidget>
#include<qtablewidget.h>
#include<QVBoxLayout>
#include<QHBoxLayout>
#include<qheaderview.h>
#include<qradiobutton.h>
#include<qbuttongroup.h>
#include<qcheckbox.h>
#include<qspinbox.h>
#include<qcombobox.h>
#include<qpushbutton.h>
#include<qlabel.h>
#include<qlineedit.h>
#include<qprogressbar.h>
#include<qmessagebox.h>
#include<qgroupbox.h>
#include<qstring.h>
#include<qstringlist.h>
#include<qtimer.h>
#include<qscrollarea.h>
class QTableWidgetDemo : public QWidget
{
Q_OBJECT
public:
QTableWidgetDemo(QWidget *parent = nullptr);
~QTableWidgetDemo();
private:
QTableWidget* table;
QProgressBar* progressBar;
QTimer* timer;
//初始化数据
void initSampleData();
//增加行
void addRow();
//删除行
void deleteRow();
//查找数据
void searchTable(const QString& name, const QString& gender, int minAge, const QString& dept);
//保存数据
void saveData();
};
QTableWidgetDemo.cpp
#include "QTableWidgetDemo.h"
QTableWidgetDemo::QTableWidgetDemo(QWidget *parent)
: QWidget(parent)
{
resize(500, 600);
//主布局
QVBoxLayout* mainLayout = new QVBoxLayout(this);
//控制区
QGroupBox* controlGroup = new QGroupBox("表格控制",this);
QGridLayout* controlLayout = new QGridLayout(controlGroup);
controlGroup->setFixedHeight(180);
//搜索框
QLineEdit* searchEdit = new QLineEdit(this);
QPushButton* searchBtn = new QPushButton("搜索", this);
controlLayout->addWidget(new QLabel("搜索:"), 0, 0);
controlLayout->addWidget(searchEdit, 0, 1);
controlLayout->addWidget(searchBtn, 0, 2);
//性别单选
QButtonGroup* genderGroup = new QButtonGroup(this);
QRadioButton* maleRadio = new QRadioButton("男", this);
QRadioButton* femaleRadio = new QRadioButton("女", this);
genderGroup->addButton(maleRadio);
genderGroup->addButton(femaleRadio);
controlLayout->addWidget(new QLabel("性别:"), 1, 0);
controlLayout->addWidget(maleRadio, 1, 1);
controlLayout->addWidget(femaleRadio, 1, 2);
//年龄范围
QSpinBox* ageSpin = new QSpinBox(this);
ageSpin->setRange(1, 120);
controlLayout->addWidget(new QLabel("年龄>:"), 2, 0);
controlLayout->addWidget(ageSpin, 2, 1);
//部门下拉
QComboBox* deptCombo = new QComboBox(this);
deptCombo->addItems({ "所有部门","技术部","市场部","财务部" });
controlLayout->addWidget(new QLabel("部门:"), 3, 0);
controlLayout->addWidget(deptCombo, 3, 1);
//操作按钮
QPushButton* addBtn = new QPushButton("添加行", this);
QPushButton* delBtn = new QPushButton("删除行", this);
QPushButton* saveBtn = new QPushButton("保存数据", this);
controlLayout->addWidget(addBtn, 4, 0);
controlLayout->addWidget(delBtn, 4, 1);
controlLayout->addWidget(saveBtn, 4, 2);
//表格区
table = new QTableWidget(0, 5, this); //初始0行5列
table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
table->setHorizontalHeaderLabels({ "ID","姓名","性别","年龄","部门" });
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
//进度条
progressBar = new QProgressBar(this);
progressBar->setFixedHeight(20);
progressBar->setVisible(false);
progressBar->setRange(0, 100);
progressBar->setVisible(false);
//布局组装
mainLayout->addWidget(controlGroup);
mainLayout->addWidget(table,1); //拉伸因子
mainLayout->addWidget(progressBar);
//功能连接
connect(addBtn, &QPushButton::clicked, this, &QTableWidgetDemo::addRow);
connect(delBtn, &QPushButton::clicked, this, &QTableWidgetDemo::deleteRow);
connect(saveBtn, &QPushButton::clicked, this, &QTableWidgetDemo::saveData);
connect(searchBtn, &QPushButton::clicked, [=]() {
searchTable(searchEdit->text(),
maleRadio->isChecked() ? "男" : "女",
ageSpin->value(),
deptCombo->currentText());
});
//初始化数据
initSampleData();
}
QTableWidgetDemo::~QTableWidgetDemo()
{}
//初始化数据
void QTableWidgetDemo::initSampleData() {
QStringList names = { "张三","李四","王五" };
QStringList genders = { "男","女","男" };
QStringList depts = { "技术部","市场部","财务部" };
for (int i = 0; i < 3; ++i) {
addRow();
table->item(i, 0)->setText(QString::number(1000 + i));
table->item(i, 1)->setText(names[i]);
table->item(i, 2)->setText(genders[i]);
table->item(i, 3)->setText(QString::number(20+i*5));
//特殊处理第四列(部门下拉框)
QComboBox* deptCombo = qobject_cast<QComboBox*>(table->cellWidget(i, 4));
if (deptCombo) {
//找到匹配的部门索引
int index = deptCombo->findText(depts[i]);
if (index >= 0) {
deptCombo->setCurrentIndex(index);
}
else {
//添加新部门并选中
deptCombo->addItem(depts[i]);
deptCombo->setCurrentIndex(deptCombo->count() - 1);
}
}
}
}
//增加行
void QTableWidgetDemo::addRow() {
int row = table->rowCount();
table->insertRow(row);
//ID列(不可编辑)
QTableWidgetItem* idItem = new QTableWidgetItem(QString::number(1000 + row));
idItem->setFlags(idItem->flags() ^ Qt::ItemIsEditable);
table->setItem(row, 0, idItem);
//其他列
table->setItem(row, 1, new QTableWidgetItem("新员工"));
table->setItem(row, 2, new QTableWidgetItem("男"));
table->setItem(row, 3, new QTableWidgetItem("25"));
//部门使用下拉框
QComboBox* deptCombo = new QComboBox();
deptCombo->addItems({ "技术部","市场部","财务部" });
table->setCellWidget(row, 4, deptCombo);
}
//删除行
void QTableWidgetDemo::deleteRow() {
if (table->currentRow() >= 0) {
table->removeRow(table->currentRow());
}
else {
QMessageBox::warning(this, "警告", "请先选择要删除的行");
}
}
//查找数据
void QTableWidgetDemo::searchTable(const QString& name, const QString& gender,int minAge,const QString& dept) {
for (int row = 0; row < table->rowCount(); row++) {
bool nameMatch = table->item(row, 1)->text().contains(name);
bool genderMatch = (gender == "所有" || table->item(row, 2)->text() == gender);
bool ageMatch = table->item(row, 3)->text().toInt() >= minAge;
bool deptMatch = true;
if (dept != "所有部门") {
QComboBox* combo = qobject_cast<QComboBox*>(table->cellWidget(row, 4));
deptMatch = combo && (combo->currentText() == dept);
}
table->setRowHidden(row, !(nameMatch && genderMatch && ageMatch && deptMatch));
}
}
//保存数据
void QTableWidgetDemo::saveData() {
progressBar->setVisible(true);
progressBar->setValue(0);
if (timer) {
timer->stop();
timer->deleteLater();
}
timer = new QTimer(this);
connect(timer, &QTimer::timeout, [=] {
static int progress = 0;
progress += 5;
progressBar->setValue(progress);
if (progress >= 100) {
timer->stop();
progressBar->setVisible(false);
QMessageBox::information(this, "完成", "数据保存成功!");
// 模拟实际保存操作
qDebug() << "=== 保存的数据 ===";
for (int row = 0; row < table->rowCount(); row++) {
QString dept = qobject_cast<QComboBox*>(table->cellWidget(row, 4))->currentText();
qDebug() << table->item(row, 0)->text() << table->item(row, 1)->text()
<< table->item(row, 2)->text() << table->item(row, 3)->text()
<< dept;
}
}
});
timer->start(200);
}
效果演示
初始界面:
添加行:
删除行:
精确搜索:
保存数据:
5.总结
QProgressBar 适合用于进度展示,可设置范围、值、显示格式,支持在任务执行过程中实时更新;
QTableWidget 提供了强大的表格功能,支持插入行列、设置单元格内容、自定义控件嵌入(如 QComboBox)、查找过滤等;
在综合案例中,我们模拟了一个典型的数据录入与管理场景,涵盖了数据初始化、增删查、控件联动与动态展示;
特别注意:当使用 setCellWidget() 嵌入控件后,不能再通过 setItem() 或 item()->setText() 操作对应单元格。