异步显示 Qt 对话框

Posted

技术标签:

【中文标题】异步显示 Qt 对话框【英文标题】:Show Qt dialog asynchronously 【发布时间】:2018-03-09 04:52:39 【问题描述】:

我想在调用 show() 后立即显示 QT 对话框;无需等待函数结束。

void SomeFunction()

dialog_.reset(new MessageBoxProgression(this, SLOT(cancel()));
dialog_->show();// not displayed waits for longOperation() to finish
longOperation();

dialog_ 有一个进度条,需要异步显示和更新,但目前,dialog_ 直到 longOperation() 执行完成后才会显示。

编辑:可以这样做吗?

void SomeFunction()

dialog_.reset(new MessageBoxProgression(this, SLOT(cancel()));
dialog_->show();// not displayed waits for longOperation() to finish

QApplication::processEvents();
longOperation();

update(dialog_);
QApplication::processEvents();

longOperation2();

【问题讨论】:

使用:qApp->processEvents();或者在另一个线程中运行longOperation(),一般第二种最合适 How to make Qt work when main thread is busy?的可能重复 我看代码没有问题,你可以做 将长操作移动到不同的线程。 QApplication::processEvents 是一个可怕的想法,应该避免。 【参考方案1】:

主线程不应该做除了 gui 工作之外的任何事情。长时间运行的操作根本不属于主线程。如果你有一个长时间运行的操作,你应该异步执行它。

我们可以分解出一些常见的长操作特征:

class LongOperationBase : public QObject 
  Q_OBJECT
  std::atomic_bool stop, running;
protected:
  bool shouldRun() const  return !stop; 
  virtual void compute() = 0;
public:
  Q_SLOT void start() 
    stop = false;
    emit started();
    QtConcurrent::run([this]
      if (running || stop) return;
      running = true;
      compute();
      running = false;
    );
  
  LongOperationBase() 
  LongOperationBase(QProgressDialog *pd) 
    connectTo(pd);
  
  bool isRunning() const  return running; 
  Q_SLOT void cancel()  stop = true;  // thread-safe
  Q_SIGNAL void started();
  Q_SIGNAL void hasRange(int);
  Q_SIGNAL void hasProgress(int);
  void connectTo(QProgressDialog *pd) 
    using B = LongOperationBase;
    connect(this, &B::started, pd, &QProgressDialog::show);
    connect(this, &B::hasRange, pd, &QProgressDialog::setMaximum);
    connect(this, &B::hasProgress, pd, &QProgressDialog::setValue);
    connect(pd, &QProgressDialog::canceled, this, &B::cancel, Qt::DirectConnection);
  
;

假设您有以下长操作 - 输入和输出数据类型仅作为示例给出。每次循环迭代应该花费 1-10 毫秒,以最大限度地减少检查状态和发出进度的开销。您可以轻松地在每个M 迭代中执行这些检查。

struct LongOperation final : LongOperationBase 
  std::vector<int> input;
  std::vector<double> output;
  using LongOperationBase::LongOperationBase;
  void compute() override 
    size_t const N = input.size();
    size_t const M = 1;
    emit hasRange(N);
    for (size_t i = 0, j = 0; i < N; ++i, ++j) 
      // operate on input, generate output
      ...
      if (j == (M-1)) 
        emit hasProgress(i);
        if (!shouldRun()) break;
        j = 0;
      
    
  
;

然后,异步执行它:

class Window : public QWidget 
  Q_OBJECT
  QProgressDialog m_progressthis;
  LongOperation m_operation&m_progress;
  ...
public:
  Window(QWidget * parent = ) : QWidget(parent) 
  void runLongOperation() 
    m_operation.start();
  
  ...
;

this answer 中给出了使用QFuture 系统的替代方法,但它还不足以简化进度指示。上述基于QObject 的方法是临时可行的解决方案。

【讨论】:

【参考方案2】:

只需添加 QApplication::processEvents();

void SomeFunction()

    dialog_.reset(new MessageBoxProgression(this, SLOT(cancel()));
    dialog_->show();// not displayed waits for longOperation() to finish
    QApplication::processEvents(); 
    longOperation();

这样就可以解决问题了

【讨论】:

dialog_ 无响应,dialog_ 内的按钮无法点击。 @SatyamRaikar 如果不使用 QApplication::processEvents() 是否有响应? @SatyamRaikar 他们不会点击,因为事件循环被阻止了。 QApplication::processEvents 会告诉它只处理一次事件,当它被调用时。无论如何,您都应该避免使用processEvents,因为它会在您的应用程序中产生各种问题。将繁重的处理任务转移到不同的线程,这样您就不会再阻塞主事件循环了。

以上是关于异步显示 Qt 对话框的主要内容,如果未能解决你的问题,请参考以下文章

Eric IDE,Qt 文件对话框:显示隐藏文件

在异步方法中显示进度对话框

在Qt中显示对话框的密码

如何将输入焦点设置为 Qt 中显示的对话框?

如果异步任务被解除,则重新显示进度对话框

QT对话框打开图像并用QPixmap显示