QT - 主窗口不会更新,除非它已关闭

时间:2017-09-04 11:02:42

标签: c++ qt user-interface

我试图通过每隔500毫秒调用一个线程中的updateGUI函数来更新主窗口。除非我关闭窗口,否则会显示窗口但不会使用新值更新。当我这样做时,会打开一个带有新值的新窗口。我找到了this question,但它没有回答我的问题。我知道(如qt文档中所述)

  

QApplication::exec进入主事件循环并等待直到   exit()被称为。{/ p>

我尝试使用processEvents(),但主窗口反复打开和关闭,非常快,我甚至看不到它。这是我的代码:

float distanceToObject;
bool objectDetected;
Modes currentMode;

void timerStart(std::function<void(void)> func, unsigned int interval)
{
    std::thread([func, interval]()
    {
        while (true)
        {
            auto x = std::chrono::steady_clock::now() + std::chrono::milliseconds(interval);
            func();
            std::this_thread::sleep_until(x);
        }
    }).detach();
}

int updateGUI(void)
{
    int argc = 0;
    char **argv = NULL;

    QApplication a(argc, argv);
    MainWindow w;
    // Set text of a label
    w.setDistance(QString::number(distanceToObject));
    // Also update objectDetected and currentMode values
    w.show();
    //a.processEvents();
    return a.exec();
}

void sendMsg(void)
{
    // Send heartbeat signal to another device
}

void receiveMsg(void)
{
    // Read messages from the other device and update the variables
    // These two values change continuously
    objectDetected = true;
    distanceToObject = 5.4;
}

void decide(void)   
{
    // The core function of the program. Takes relatively long time
    // Run a decision-making algorithm which makes decisions based on the values received from the other device. 
    // Update some variables according to the made decisions
    currentMode = Auto;
    // Execute functions according to the made decisions. 
    setMode(currentMode);
}

int main(void)
{
    timerStart(updateGUI, 500);
    timerStart(sendMsg, 1000);
    timerStart(receiveMsg, 10); 
    timerStart(decide, 500);
}

如何正确更新变量值的主窗口?

1 个答案:

答案 0 :(得分:7)

您的主题不会更新MainWindow,但它会在每次迭代时创建一个全新的QApplicationMainWindow。您的线程应该卡在QApplication::exec内,直到您退出应用程序(例如关闭窗口)。只有这样,你的线程循环才能进一步发展。

通常,在从主线程外部进行更新时必须非常小心,因为通常必须在主线程内执行GUI操作。

考虑使用QThread,它已经有自己的事件循环,您可以使用它来通过相应的插槽通知/更新您的窗口。

如果没有关于您实际想要实现的目标的更多详细信息,则无法为您提供进一步的指导。我至少建议您在主线程中创建QApplicationMainWindow(例如main)。那么这取决于你想要的更新&#39;。如果您需要处理某些数据,则可以在第二个线程中执行此操作,并使用信号槽将结果发送到MainWindow实例。如果你需要绘制到窗口,那么这必须直接在主线程中完成,或者你可能会找到一种方法从你的线程中渲染到一个单独的缓冲区(即QImage)然后发送这个缓冲到主线程以将其绘制到窗口中。

我试着勾勒出这样的事情。但请注意,这既不完整也不可编译,只是一个大纲。

首先,您拥有MainWindow并添加signal,通知所有观察者开始工作(稍后会变得清晰)。此外,您添加slots,只要您的某个值发生更改,就会调用该slots。那些MainWindow在主线程中运行(并且是class MainWindow : public QMainWindow { Q_OBJECT public: // constructors and stuff void startWorking() { emit startWorkers(); } public slots: void onModeChanged(Modes m) { // update your window with new mode } void onDistanceChanged(float distance) { // update your window with new distance } signals: void startWorkers(); }; 的成员),因此可以更新窗口但是需要:

Worker

接下来,您将构建一个class Worker : public QObject { Q_OBJECT public: // constructors and stuff public slots: void doWork() { while(!done) { // do stuff ... Modes m = // change mode emit modeModified(m); // do stuff ... float distance = // compute distance emit distanceModified(distance); // do stuff ... } } signals: void modeModified(Modes m); void distanceModified(float distance); }; 类,其中包含了所有&#39;后台工作&#39;你喜欢这样做(基本上是你的主题在原始代码中做了什么):

Worker

注意,QObject必须继承doWork,并且您的public slot方法必须是signal。此外,您为MainWindow要通知的每个值添加emit。不需要实现它们,因为它是由Qt MOC(元对象编译器)生成的。只要其中一个值发生变化,只需signal相应的int main(int argc, char* argv[]) { QApplication app(argc, argv); MainWindow window; // create a worker object Worker* worker = new Worker; // connect signals and slots between worker and main window QObject::connect(worker, &Worker::modeModified, &window, &MainWindow::onModeChanged); QObject::connect(worker, &Worker::distanceModified, &window, &MainWindow::onDistanceChanged); QObject::connect(&window, &MainWindow::startWorkers, worker, &Worker::doWork); // create a new thread QThread* thread = new QThread; // send worker to work inside this new thread worker->moveToThread(thread); thread->start(); // show window and start doing work window.show(); window.startWorking(); // start main loop int result = app.exec(); // join worker thread and perform cleanup return result; } 并传递新值。

最后,你把所有东西放在一起:

QApplication

好的,让我们来看看吧。首先,在主线程中创建MainWindowWorker。接下来,创建connect对象的实例(可以在此处创建多个)。然后workerwindow的信号发送到emit的广告位,反之亦然。建立这些连接后,无论何时worker发出信号,Qt都会调用连接的槽(并传输传递的值)。请注意,此连接跨线程边界。每当从与接收对象的线程不同的线程发出信号时,Qt将发送消息,该消息在接收对象的线程中处理。

然后你告诉Qt你希望你的QObject::moveToThread使用QThread住在另一个线程中。有关如何正确使用show及其中的对象的详细说明,请参阅here

其余的很简单。 window startWorking并开始处理。这里有不同的方法。我只是在这里调用emit方法,然后startWorkers worker信号,它连接到doWork的{​​{1}}方法,这样,doWork将在另一个线程接收到该信号后开始执行。

然后调用运行主线程事件循环的QApplication::exec,其中所有这些信号都由Qt处理。关闭应用程序后(例如,通过调用quit或关闭主窗口),exec方法将返回,您将返回main。请注意,您需要正确关闭线程(例如,通过发送阻止while循环的附加信号)并加入它。您还应删除所有已分配的对象(workerthread)。为了简化代码示例,我在此省略了这一点。

回答你的问题

  

我有很多函数,例如updateClips和mavReceive,它们应该定期调用并相互独立运行。我应该为每个函数创建一个不同的Worker类,因为每个函数都有不同的信号,并且每个函数都有一个QThread对象,对吧?我不再需要startTimer()了吗?如果是,我如何控制每个函数的调用间隔(以前在startTimer()

中完成

来自评论:

答案很大程度上取决于你究竟是什么意思&#34;应该定期调用&#34;。谁应该打电话给他们?用户?或者他们应该定期执行?

原则上,您可以在一个线程中拥有多个工作线程。但是,如果它们应该一直工作(在while循环中旋转)它没有意义,因为一个正在运行而所有其他都被阻止。在这种情况下,每个工作人员都有一个线程。

如果我理解正确,您有兴趣定期更新某些内容(例如每500毫秒)。在这种情况下,我强烈建议使用QTimer。您可以设置间隔然后启动它。然后,计时器将定期emit timeout信号,您可以连接到您想要执行的任何功能(更准确地说是slot)。

Worker的更新版本可能如下所示:

class Worker : public QObject
{
    Q_OBJECT
public:
    Worker()
    {
        QObject::connect(&modeTimer_, &QTimer::timeout,
            this, &Worker::onModeTimerTimeout);
        QObject::connect(&distanceTimer_, &QTimer::timeout,
            this, &Worker::onDistanceTimerTimeout);

        modeTimer_.start(500); // emit timeout() every 500ms
        distanceTimer_.start(100); // emit timeout() every 100ms
    }

public slots:    
    void onModeTimerTimeout()
    {
        // recompute mode
        Modes m = // ...
        emit modeModified(m);
    }

    void onDistanceTimerTimeout()
    {
        // recompute distance
        float distance = // ...
        emit distanceModified(distance);
    }

signals:
    void modeModified(Modes m);
    void distanceModified(float distance);

private:
    QTimer modeTimer_;
    QTimer distanceTimer_;
};

注意,在构造函数中建立的连接。每当其中一个计时器超时,就会调用连接的slot。然后,此插槽可以计算它需要的任何内容,然后使用与之前相同的MainWindow将结果发送回主线程中的signal

因此,如您所见,您可以在一个Worker(因此,一个线程)内拥有多个定时器/重新计算/更新信号。但是,实现的关键点是计算需要多长时间。如果它们花费很长时间(例如几乎与间隔一样长),那么您应该考虑使用多个线程来加速计算(意味着:在每个线程中执行一次计算)。当我慢慢地想要更清楚地了解您想要实现的目标时,我想知道是否只是关于这些定期更新您是否被滥用&#39;你问题中的主题。如果情况确实如此,那么您根本不需要该线程和Worker。然后,只需将计时器添加到MainWindow,然后将timeout信号直接连接到slot的{​​{1}}。