2017-09-04 45 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

添加了一個草圖和一些方法來做一些你似乎嘗試的東西的解釋。 –