2013-05-11 112 views
9

我有一個多線程的qt應用程序。當我在mainwindow.cpp中進行一些進程時,同時我想從其他線程更新mainwindow.ui。Qt - 用第二個線程更新主窗口

我有mythread.h

#ifndef MYTHREAD_H 
#define MYTHREAD_H 
#include <QThread> 
#include "mainwindow.h" 

class mythread : public QThread 
{ 
    public: 
     void run(); 
     mythread(MainWindow* ana); 
    MainWindow* ana; 
private: 

}; 

#endif // MYTHREAD_H 

mythread.cpp

mythread::mythread(MainWindow* a) 
{ 
    cout << "thread created" << endl; 
     ana = a; 
} 

void mythread::run() 
{ 
    QPixmap i1 (":/notes/pic/4mdodiyez.jpg"); 
    QLabel *label = new QLabel(); 
    label->setPixmap(i1); 
    ana->ui->horizontalLayout_4->addWidget(label); 


} 

但問題是,我不能達到ana->ui->horizontalLayout_4->addWidget(label);

我該怎麼辦呢?

+0

如果你和我一樣,而且你有10分鐘的截止日期,這裏是一個更加黑客的解決方案:在主窗口中添加一個虛擬按鈕(寬度和高度爲0),每當你需要從工作人員更新UI時在worker中發出click()事件並覆蓋該按鈕的click處理程序以執行更新。 – cristid9 2017-02-15 18:00:38

回答

12

但問題是,我不能達到 ana-> UI-> horizo​​ntalLayout_4-> addWidget(標籤);

把你的用戶界面修改放在你的主窗口中的一個槽中,並且將一個線程信號連接到那個槽,很可能會起作用。我認爲只有主線程才能訪問Qt中的UI。因此,如果你想要GUI功能,它必須在那裏,並且只能從其他線程發出信號。

好的,這裏是一個簡單的例子。順便說一句,你的情況並不需要延長QThread - 所以你最好不要這樣做,除非你真的必須這樣做。這就是爲什麼在這個例子中,我將使用一個正常的QThreadQObject基於工人代替,但概念是相同的,如果你繼承QThread

的主界面:

class MainUI : public QWidget 
{ 
    Q_OBJECT 

public: 
    explicit MainUI(QWidget *parent = 0): QWidget(parent) { 
     layout = new QHBoxLayout(this); 
     setLayout(layout); 
     QThread *thread = new QThread(this); 
     GUIUpdater *updater = new GUIUpdater(); 
     updater->moveToThread(thread); 
     connect(updater, SIGNAL(requestNewLabel(QString)), this, SLOT(createLabel(QString))); 
     connect(thread, SIGNAL(destroyed()), updater, SLOT(deleteLater())); 

     updater->newLabel("h:/test.png"); 
    } 

public slots: 
    void createLabel(const QString &imgSource) { 
     QPixmap i1(imgSource); 
     QLabel *label = new QLabel(this); 
     label->setPixmap(i1); 
     layout->addWidget(label); 
    } 

private: 
    QHBoxLayout *layout; 
}; 

...和工人對象:

class GUIUpdater : public QObject { 
    Q_OBJECT 

public: 
    explicit GUIUpdater(QObject *parent = 0) : QObject(parent) {}  
    void newLabel(const QString &image) { emit requestNewLabel(image); } 

signals:  
    void requestNewLabel(const QString &); 
}; 

工人對象被創建並移動到另一個線程,然後連接到該創建的標籤插槽,然後其newLabel方法被調用,這僅僅是爲發射0的包裝信號並將路徑傳遞給圖像。該信號隨後從工作對象/線程傳遞到主UI槽以及圖像路徑參數,並將新標籤添加到佈局。

由於worker對象是在沒有父對象的情況下創建的,爲了能夠將其移動到另一個線程,我們還將線程被破壞的信號連接到工作人員deleteLater()插槽。

+0

您可以舉一個連接示例嗎?我無法決定連接的元素。 – abby 2013-05-12 08:31:42

+0

@abby - 我添加了一個簡單的例子來說明如何實現你所需要的。 – dtech 2013-05-12 10:14:04

+0

我試過這個,但不是加載一個圖像,我只是在newLabelText()(必須修改newLabel(),requestNewLabel())中進行一些計算,以查看這是否實際解決了在廣泛使用線程時凍結UI的常見問題後臺工作(如作者所建議的他/她想做的)。事實上,它確實凍結了整個事情(我把一個LCD數字控制和一個按鈕用於計數並在LCD上顯示結果,以檢查它是否確實凍結)。這也意味着,如果我們加載一個非常大的圖像,這可能會導致阻止用戶界面。 – rbaleksandar 2014-04-25 15:15:43

3

首先,"you're doing it wrong"。通常情況下,你想創建一個派生自QObject的類,並將該類移動到一個新的線程對象,而不是從Qthread派生你的類。現在要了解你的問題的具體細節,你不能直接修改您的主GUI線程的UI元素來自單獨的線程。你必須從你的第二線程connect a signal到你的主線程中的slot。您可以通過此信號/插槽連接傳遞所需的任何數據,但無法直接修改ui元素(實際上,如果您打算將應用程序的前端與後端分開,則可能不希望這樣做)。結帳Qt的信號和槽documentation用於很多更多信息

+0

有沒有辦法從工作線程更新mainwindow.ui?因爲我真的需要它。 – abby 2013-05-12 08:12:03

+0

你會更新連接到來自不同線程的信號的插槽中的UI元素 – g19fanatic 2013-05-13 02:03:38

+0

啊,臭名昭着的「你做錯了」博客文章。我強烈反對他。請參閱http://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html – 2013-11-26 05:31:52

1

我該怎麼做?

你已經得到了你應該做的答案,但不是爲什麼,所以我要添加一個原因。

你不從另一個線程修改GUI元素的原因是因爲GUI元素通常不是thread-safe。這意味着如果你的主GUI線程和你的工作線程都更新了UI,你不能確定什麼時候發生什麼。

對於閱讀數據通常這可能是罰款(例如檢查條件),但通常你不希望這是情況。對於編寫數據而言,這幾乎總是「隨機」發生的非常非常緊張的錯誤的來源。

另一個答案已經提到了良好的設計原則 - 不僅將GUI邏輯約束到一個線程並觸發信號與它交談,擺脫了競爭條件問題,而且還迫使您很好地劃分代碼。然後可以將顯示邏輯(顯示位)和數據處理邏輯完全分離出來,這使得維護兩者變得更容易。

在這個階段你可能會想:哎呀,這個線程業務是farrrrrr工作太多了!我會避免這種情況。要了解爲什麼這是一個糟糕的主意,請使用簡單的進度條在單個線程中實現文件複製程序,告訴您複製的內容有多遠。在一個大文件上運行它。在Windows上,過了一段時間,應用程序將「變白」(或在XP上,我認爲它變灰),並且將是「沒有響應」。這實際上是發生了什麼事情。

GUI應用程序內部主要處理「一個大循環」處理和分派消息的變體。例如,Windows會測量這些消息的響應時間。如果消息需要很長時間才能得到響應,則Windows會判定它已經死機,並接管。 This is documented in GetMessage()。因此,雖然它看起來像一個相當多的工作,信號/槽(一個事件驅動模型)基本上是要走的路 - 另一種方式想到這一點,它是完全可以接受你的線程產生用戶界面的「事件」 - 比如進度更新等。