在函数调用期间临时禁用窗口

Posted

技术标签:

【中文标题】在函数调用期间临时禁用窗口【英文标题】:Temporarily disable window during a function call 【发布时间】:2020-06-01 09:51:37 【问题描述】:

考虑:

class Worker

public:
  void DoWork();
;

class MainWindow : public QMainWindow

Q_OBJECT

private:
  Worker MyWorker;

public:
  void DoWork();
;

我希望我的MainWindowDoWork 函数的行为类似于:

void MainWindow::DoWork()

  setEnabled(false);

  std::future<void> Resu = std::async(std::launch::async, &Worker::DoWork, &MyWorker);

  while (Resu.wait_for(100ms) == std::future_status::timeout)
    qApp->processEvents();

  try
     Resu.get(); 
  catch (Except const &MyExcept)
     QMessageBox::critical(this, "Error", MyExcept.What()); 

   setEnabled(true);
 

这可以按我的意愿工作:(i)在工作时窗口被禁用, (ii) 窗口在工作时保持响应(可以移动和调整大小),以及 (iii) 任何 Except 扔在Worker::DoWork 里很方便。

但是,此解决方案依赖于 std::futureqApp-&gt;processEvents(),后者 不被this answer推荐。

如何在 Qt 中正确编写上述代码?

到目前为止,我已经尝试过:

void MainWindow::DoWork()

  setEnabled(false);

  QFuture<void> Resu = QtConcurrent::run(std::bind(&Worker::DoWork, &MyWorker));

  try
     while (Resu.isRunning()) qApp->processEvents(); 
  catch (Except const &MyExcept)
     QMessageBox::critical(this, "Error", MyExcept.What()); 

   setEnabled(true);
 

我发现了消耗整个线程的缺点(因为过于频繁地调用qApp-&gt;processEvents()),而且异常没有被正确捕获。

【问题讨论】:

【参考方案1】:

您可以使用QFutureWatcher 接收有关您的QFuture 完成的通知,而无需忙于等待呼叫processEvents

基本上您订阅信号QFutureWatcher&lt;void&gt;::finished 以了解操作何时完成。在关联的插槽中,您重新启用小部件。特别重要的是调用future.waitForFinished(),即使计算已经完成,以强制转移在工作线程中抛出的任何QException,请参阅here。这看起来像是一种解决方法,但我不知道是否有更好的解决方案。

订阅后您调用setFuture() 函数开始您的doWork 操作。计算将在自己的线程中进行。

这样你应该可以为所欲为:

    工作时窗口被禁用 窗口在工作时保持响应(可以移动和调整大小) Worker::doWork 中的任何 QException 都可以方便地捕获

根据您的问题,这是一个最小的工作示例。它应该告诉你我的意思......

#include <QApplication>
#include <QException>
#include <QMessageBox>
#include <QPushButton>
#include <QThread>
#include <QtConcurrent>


class Exception : public QException

public:
    Exception(const QString& message = "") : m_message message  

    void raise() const override  throw *this; 

    Exception *clone() const override  return new Exception(*this); 

    QString message()  return m_message; 

private:
    QString m_message;
;


class Worker

public:
    void doWork() 
        QThread::sleep(5);
        // throw Exception "something bad happened...";
        QThread::sleep(5);
    
;


class Button : public QPushButton

    Q_OBJECT

public:
    Button(QWidget* parent = nullptr) : QPushButton parent 
        resize(400, 300);
        setText("Click me to wait!");

        connect(this, &QPushButton::clicked,
                this, &Button::doWork);
    

public slots:
    void doWork() 
        setEnabled(false);

        auto future QtConcurrent::run(&m_worker, &Worker::doWork);

        auto watcher new QFutureWatcher<void> ;
        connect(watcher, &QFutureWatcher<void>::finished,
                [=]() mutable 
                    try 
                        // Call this to force the transfer of any QException that was thrown in the worker thread
                        // (https://doc.qt.io/qt-5/qexception.html)
                        future.waitForFinished();
                     catch (Exception& e) 
                        QMessageBox::critical(this, "Error", e.message());
                    
                    setEnabled(true);
                );

        watcher->setFuture(future);
    

private:
    Worker                  m_worker;
;


int main(int argc, char* argv[])

    QApplication app argc, argv;

    auto button new Button ;
    button->show();

    return app.exec();


#include "main.moc"

【讨论】:

异常必须从QException 继承的限制在我的情况下有点限制。无论如何,现在我非常清楚在 Qt 中这样做的正确方法是什么。 我完全同意你的看法。我也认为这在某些情况下是一个限制。也许有更好的方法来管理这个,但我找不到它们 =)

以上是关于在函数调用期间临时禁用窗口的主要内容,如果未能解决你的问题,请参考以下文章

在cmocka中临时启用和禁用功能包装的正确方法?

C语言中调用system()函数弹出dos窗口如何隐藏?

返回函数调用与仅在递归期间再次调用函数有啥区别?

在 Pyside 中执行函数期间等待按钮单击

为啥必须在 componentDidMount() 期间解析函数中的函数调用?

在破坏调用期间从另一个线程未定义的行为调用对象上的方法?