You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt使用QtConcurrent调用QuaZIP压缩时如何显示运行中提示消息

Qt 文件夹压缩过程显示提示消息解决方案

问题背景

现有基于Qt + QuaZIP实现的文件夹压缩功能,通过QtConcurrent异步调用compress方法执行压缩,基础功能正常,需要在压缩过程中显示运行提示/进度。

初始基础代码如下:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
  Q_OBJECT

public:
  Widget(QWidget *parent = nullptr);
  ~Widget();
 
private slots:
  void on_pushButton_5_clicked();

private:
  Ui::Widget *ui;
  void compress();
};
#endif // WIDGET_H

// widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>
#include <JlCompress.h>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>

Widget::Widget(QWidget *parent)
  : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this); 
}
Widget::~Widget()
{
  delete ui;
}
void Widget::on_pushButton_5_clicked()
{
  QGuiApplication::setOverrideCursor(Qt::WaitCursor);
  QFuture<void> future=QtConcurrent::run(this,&Widget::compress);
  future.waitForFinished();
  QGuiApplication::restoreOverrideCursor();
  QMessageBox::information(this,qApp->applicationName(),"The folder was compressed successfully.");
}
void Widget::compress()
{
  if(!JlCompress::compressDir("C:/Users/Mypc/Desktop/iconos.zip", "D:/images/iconos")){
    QMessageBox::warning(this,qApp->applicationName(),"Error compressing folder.");
    return;
  }
}

踩坑问题分析与修复

问题1:添加提示框后程序直接崩溃

错误代码

void Widget::on_pushButton_5_clicked()
{
  QMessageBox box;
  box.setStandardButtons(QMessageBox::NoButton);
  box.setText("Realizando una tarea!");
  box.setWindowTitle(qApp->applicationName());
  box.setIcon(QMessageBox::Information);
  box.setAttribute(Qt::WA_DeleteOnClose);

  QFutureWatcher<void> watcher;
  QFuture<void> future=QtConcurrent::run(this,&Widget::compress);
  watcher.setFuture(future);

  QGuiApplication::setOverrideCursor(Qt::WaitCursor);

  QObject::connect(&watcher,&QFutureWatcher<void>::finished,[&](){
    box.close();
    box.deleteLater();
    QGuiApplication::restoreOverrideCursor();
  });
  box.exec();
  QMessageBox::information(this,qApp->applicationName(), "The folder was compressed successfully.");
}

错误原因

  1. 子线程中操作UI:compress方法运行在异步子线程,直接调用QMessageBox::warning属于跨线程操作UI,Qt不允许,直接触发崩溃。
  2. 栈对象设置自动删除属性:栈上创建的QMessageBox box设置了Qt::WA_DeleteOnClose,窗口关闭时Qt会尝试delete栈内存,触发内存错误。
  3. lambda引用捕获野指针:lambda用引用捕获栈上的box和watcher,lambda执行时变量可能已经出作用域销毁,访问野指针触发崩溃。

修复代码

首先修改头文件,新增压缩完成信号:

// widget.h 新增内容
signals:
    void compressFinished(bool success);

修改compress方法,移除子线程UI操作,通过信号返回压缩结果:

void Widget::compress()
{
    bool res = JlCompress::compressDir("C:/Users/Mypc/Desktop/iconos.zip", "D:/images/iconos");
    emit compressFinished(res);
}

修改按钮点击逻辑:

void Widget::on_pushButton_5_clicked()
{
    // 用堆分配提示框,自动回收
    QMessageBox *box = new QMessageBox(this);
    box->setStandardButtons(QMessageBox::NoButton);
    box->setText("正在执行压缩任务!");
    box->setWindowTitle(qApp->applicationName());
    box->setIcon(QMessageBox::Information);
    box->setAttribute(Qt::WA_DeleteOnClose);

    // 堆分配watcher,自动回收
    QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
    QFuture<void> future = QtConcurrent::run(this, &Widget::compress);
    watcher->setFuture(future);

    QGuiApplication::setOverrideCursor(Qt::WaitCursor);

    // 主线程处理压缩结果
    connect(this, &Widget::compressFinished, this, [=](bool success){
        box->close();
        QGuiApplication::restoreOverrideCursor();
        if(success){
            QMessageBox::information(this, qApp->applicationName(), "文件夹压缩成功。");
        }else{
            QMessageBox::warning(this, qApp->applicationName(), "文件夹压缩失败。");
        }
        watcher->deleteLater();
    });

    box->exec();
}

问题2:QProgressDialog实现进度提示编译报错

错误代码

void Widget::on_pushButton_5_clicked()
{
  QStringList files;
  QDir dir("D:/images/iconos/uigenericicon");
  files=dir.entryList(QStringList() << "*.jpg" << "*.JPG"<<"*.PNG",QDir::Files);
  qInfo()<<files;

  QProgressDialog pDialog;
  pDialog.setLabelText("Porecessing!!");
  QFutureWatcher<void> watcher;

  QObject::connect(&pDialog,SIGNAL(canceled()),&watcher,SLOT(cancel()));
  QObject::connect(&watcher,SIGNAL(finished()),&pDialog,SLOT(reset()));
  QObject::connect(&watcher,SIGNAL(progressRangeChanged(int,int)),&pDialog,SLOT(setRange(int,int)));
  QObject::connect(&watcher,SIGNAL(progressValueChanged(int)),&pDialog,SLOT(setValue(int)));

  watcher.setFuture(QtConcurrent::map(files,&Widget::compress));
  pDialog.exec();
  if(watcher.isCanceled()){
    QMessageBox::information(this,qApp->applicationName(),"You canceled.");
  }else{
    QMessageBox::information(this,qApp->applicationName(),"All done.");
  }
}

void Widget::compress(QStringList &files)
{
  if(!JlCompress::compressFiles("C:/Users/Lincoln/Desktop/iconos.zip",files)){
    QMessageBox::warning(this,qApp->applicationName(),"Error al comprimir la carpeta.");
    return;
  }
}

报错截图

报错截图

错误原因

QtConcurrent::map是对容器内每个元素单独调用处理函数,要求处理函数入参是容器单个元素的类型,你这里files是QStringList,单个元素是QString,但compress方法入参是QStringList&,类型不匹配,直接编译报错。

修复代码

要实现进度提示,需要自己遍历文件压缩,逐文件更新进度:
首先修改头文件:

// widget.h 新增
signals:
    void compressProgress(int current, int total);
    void compressFinished(bool success);
private:
    bool compress(const QString& zipPath, const QString& dirPath);

实现带进度上报的压缩方法:

bool Widget::compress(const QString& zipPath, const QString& dirPath)
{
    QDir dir(dirPath);
    if(!dir.exists()) return false;
    // 筛选要压缩的文件
    QStringList files = dir.entryList(QStringList() << "*.jpg" << "*.JPG"<<"*.PNG", QDir::Files);
    int total = files.size();
    QuaZip zip(zipPath);
    if(!zip.open(QuaZip::mdCreate)) return false;
    for(int i=0; i<total; i++){
        // 检测取消操作
        if(QThread::currentThread()->isInterruptionRequested()){
            zip.close();
            return false;
        }
        QString filePath = dir.absoluteFilePath(files[i]);
        if(!JlCompress::compressFile(&zip, filePath, files[i])){
            zip.close();
            return false;
        }
        // 上报进度
        emit compressProgress(i+1, total);
    }
    zip.close();
    return true;
}

修改按钮点击逻辑:

void Widget::on_pushButton_5_clicked()
{
    QProgressDialog *pDialog = new QProgressDialog(this);
    pDialog->setLabelText("正在压缩文件...");
    pDialog->setCancelButtonText("取消");
    pDialog->setAttribute(Qt::WA_DeleteOnClose);

    QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>(this);
    QFuture<bool> future = QtConcurrent::run(this, &Widget::compress, "C:/Users/Lincoln/Desktop/iconos.zip", "D:/images/iconos/uigenericicon");
    watcher->setFuture(future);

    // 进度更新绑定
    connect(this, &Widget::compressProgress, pDialog, [pDialog](int cur, int total){
        pDialog->setRange(0, total);
        pDialog->setValue(cur);
    });
    // 取消操作绑定
    connect(pDialog, &QProgressDialog::canceled, watcher, [watcher](){
        watcher->cancel();
        watcher->future().waitForFinished();
    });
    // 完成逻辑绑定
    connect(watcher, &QFutureWatcher<bool>::finished, this, [=](){
        pDialog->close();
        if(watcher->isCanceled()){
            QMessageBox::information(this, qApp->applicationName(), "你已取消压缩操作。");
        }else{
            bool success = watcher->result();
            success ? QMessageBox::information(this, qApp->applicationName(), "压缩完成。") 
                    : QMessageBox::warning(this, qApp->applicationName(), "压缩失败。");
        }
        watcher->deleteLater();
    });

    pDialog->exec();
}

内容的提问来源于stack exchange,提问作者Lincoln Ingaroca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 13:45:02