走进Qt--QProgressBar与QTableWidget应用

238 阅读9分钟

1.前言

在现代GUI开发中,实时反馈数据可视化是提升用户体验的核心要素。无论是桌面应用、工业控制还是嵌入式系统,开发者常面临这样的需求:

  1. 需要清晰告知用户耗时操作的进度
    • 文件传输、数据导入、算法计算等场景中,用户渴望获得明确的进度预期
    • 糟糕的实现:界面卡死,用户误以为程序崩溃
    • 优雅的方案:QProgressBar动态可视化进程状态
  2. 需要高效展示和编辑结构化数据
    • 从简单的配置表格到复杂的数据看板,二维数据展示无处不在
    • 原生控件的痛点:手动实现排序、编辑、样式调整成本高昂
    • 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);
}

效果演示

界面初始效果

image.png

点击“开始下载”

image.png

下载完成

image.png

点击“取消”

image.png

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);
}

效果演示

初始界面

image.png

添加行

image.png

删除行

image.png

精确搜索

image.png

保存数据

image.png

image.png

5.总结

QProgressBar 适合用于进度展示,可设置范围、值、显示格式,支持在任务执行过程中实时更新;

QTableWidget 提供了强大的表格功能,支持插入行列、设置单元格内容、自定义控件嵌入(如 QComboBox)、查找过滤等;

在综合案例中,我们模拟了一个典型的数据录入与管理场景,涵盖了数据初始化、增删查、控件联动与动态展示;

特别注意:当使用 setCellWidget() 嵌入控件后,不能再通过 setItem()item()->setText() 操作对应单元格。