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."); }
错误原因
- 子线程中操作UI:
compress方法运行在异步子线程,直接调用QMessageBox::warning属于跨线程操作UI,Qt不允许,直接触发崩溃。 - 栈对象设置自动删除属性:栈上创建的
QMessageBox box设置了Qt::WA_DeleteOnClose,窗口关闭时Qt会尝试delete栈内存,触发内存错误。 - 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
相关产品推荐
相关产品推荐

