2017-09-04 57 views
1

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

QApplication::exec进入主事件循环并等待 exit()被调用。

我试图使用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); 
} 

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

+2

乍一看,你的代码似乎在很多方面都被破坏了...... –

+0

你想用计时器实现什么?为什么不直接从主窗口启动窗口? – SPlatten

+0

感谢您的回复。 @nh_我是qt的新手。你能指出我的代码中最重要的问题吗? @SPlatten我的代码主要运行在线程中,每个线程执行不同的功能并以不同的时间间隔调用。在主函数中,我只为不同的函数调用'timerStart'。当我从main启动窗口时,它冻结而其他线程运行。 –

回答

7

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

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

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

没有关于您实际尝试实现什么的进一步细节,不可能给您进一步的指示。至少,我建议您在主线程内创建您的QApplicationMainWindow(例如main)。那么这取决于你想要“更新”什么。如果您需要处理一些数据,那么您可以在第二个线程中执行此操作,并使用信号插槽将结果发送到您的MainWindow实例。如果你需要在窗口上绘图,那么这个任务必须直接在主线程中完成,否则你可能会找到一种方法从你的线程中渲染到一个单独的缓冲区(即QImage),然后将这个缓冲区发送到主将其绘制到窗口中的线程。


我试图描绘一下如何做这样的事情。但是请注意,它既不完整也不可编译,而仅仅是一个轮廓。

首先,你有你的MainWindow并加上signal,通知所有观察者开始工作(一会儿就会变得清晰)。此外,您还可以添加slots,只要其中一个值发生更改,就会调用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); 
}; 

注意,那Worker必须继承QObject和你doWork方法必须是一个public slot。此外,您还可以为每个喜欢MainWindow的值添加一个signal。由于它是由Qt MOC(元对象编译器)生成的,因此不需要实现它们。每当其中一个值发生变化时,只需emit对应的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; 
} 

好吧,让我们通过它去。首先,在主线程中创建您的QApplicationMainWindow。接下来,创建一个Worker对象的实例(可以在此处创建多个实例)。然后您将worker的信号添加到window的插槽中,反之亦然。一旦这些连接建立起来,每当你有一个信号时,连接的时隙就会被Qt调用(并且传送的值被传送)。注意,这个连接可以跨越线程边界。每当信号从与接收对象的线程不同的线程发出时,Qt将发送一条消息,在接收对象的线程中处理该消息。

然后,您告诉Qt,您希望您的worker使用QObject::moveToThread在另一个线程内生存。有关如何正确使用QThread及其内部对象的详细说明,请参见here

其余的就很简单。 show您的window并开始处理。这里可能有不同的方式。我只是在这里调用startWorking方法,然后emitstartWorkers信号,它连接到doWork的方法,使得doWork将在另一个线程接收到该信号之后开始执行。

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


回答你的问题

我有很多的功能,例如,updateClips和mavReceive应定期调用,相互独立运行。我应该为每个函数创建一个不同的Worker类,因为每个函数都有不同的信号,并且每个函数都有一个QThread对象,对吗?我不需要startTimer()了吗?如果是的话,我怎么能控制每个函数的调用间隔(使用与startTimer()

从注释工作要做:

答案在很大程度上取决于究竟你的意思是“应该被称为定期“,谁应该给他们打电话?用户?或者他们是否应该定期执行?

所以原则上,你可以在一个线程中有多个工作者,但是如果他们应该一直工作(在一个while循环中旋转)它是没有意义的,因为一个正在运行并且所有其他的都被阻塞了。在这种情况下,每个worker都会有一个线程。

如果我理解正确,您有兴趣定期更新某些内容(例如,每500ms)。在这种情况下,我强烈建议使用QTimer。您可以设置一个时间间隔,然后启动它。定时器将定期emittimeout信号,您可以连接到任何您想要执行的功能(更确切地说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。然后该槽可以计算它需要的任何值,然后使用与之前相同的signal将结果发送回主线程中的MainWindow。因此,正如你所看到的,你可以在一个Worker(也就是一个线程)内有多个定时器/重新计算/更新信号。然而,实现的关键点是计算需要多长时间。如果它们花费很长时间(例如几乎与区间一样长),那么您应该考虑使用多个线程来加速计算(意思是:在每个线程中执行一次计算)。当我慢慢地看到你想要获得的更清晰的图像时,我想知道是否仅仅是关于这些定期更新,你在你的问题中“滥用”了线索。如果确实如此,那么你根本不需要那个线程和Worker。然后只需将定时器添加到MainWindow,并将它们的timeout信号直接连接到MainWindow的相应slot即可。

+0

感谢@nh_您的答案。我更新了我的问题,使我想清楚。我真的开始阅读信号和插槽以及QThread,然后我发现[此链接](https://stackoverflow.com/questions/32399731/how-to-use-stdthread-with-qts-main-event-loop)这让我想到了我目前的做法。 –

+0

你能指导我的方法更适合我的情况吗?谢谢 –

+0

添加了一个草图和一些方法来做一些你似乎尝试的东西的解释。 –