2014-06-16 72 views
0

我創建了一個應用程序,我想在應用程序打開項目時添加加載屏幕,因爲項目的加載可能很長,有時候,gui會阻止用戶可以認爲存在崩潰。Qt在打開項目時添加加載小部件屏幕

因此,我嘗試與QThread,閱讀文檔和「解決」在這個論壇上的例子,但沒有做什麼,我不能讓它工作。

我有一個MainWindow類與GUI優惠和這個類是一個我在主函數創建:

int main(int argc, char *argv[]) 
{ 
    QApplication a(argc, argv); 
    MainWindow w; 
    w.show(); 
    return a.exec(); 
} 

然後,我有: mainwindow.h

class MyThread; 
class MainWindow : public QMainWindow 
{ 
    Q_OBJECT 
    ... 
    private : 
     Controller *controller;//inherits from QObject and loads the project 
     QList<MyThread*> threads; 
    public slots : 
     void animateLoadingScreen(int inValue); 
} 

主窗口。 cpp

MainWindow::MainWindow(...) 
{ 
    controller=new Controller(...); 

    threads.append(new MyThread(30, this)); 
    connect(threads[0], SIGNAL(valueChanged(int)), this, SLOT(animateLoadingScreen(int))); 
} 

void MainWindow::animateLoadingScreen(int inValue) 
{ 
    cout<<"MainWindow::animateLoadingScreen"<<endl; 
    widgetLoadingScreen->updateValue(inValue);//to update the animation 
} 

void MainWindow::openProject() 
{ 
    widgetLoadingScreen->show()://a widget containing a spinner for example 
    threads[0]->start();//I want to launch the other thread here 

    controller->openProject(); 

    threads[0]->exit(0);//end of thread so end of loading screen 
} 

MyThread.h

class MyThread : public QThread 
{ 
    Q_OBJECT; 
public: 
    explicit MyThread(int interval, QObject* parent = 0); 
    ~MyThread(); 

signals: 
    void valueChanged(int); 

private slots: 
    void count(void); 

protected: 
    void run(void); 

private: 
    int i; 
    int inc; 
    int intvl; 
    QTimer* timer; 
}; 

MyThread.cpp

MyThread::MyThread(int interval, QObject* parent): QThread(parent), i(0), inc(-1), intvl(interval), timer(0) 
{ 
} 

void MyThread::run(void) 
{ 
    if(timer == 0) 
    { 
     timer = new QTimer(this); 
     connect(timer, SIGNAL(timeout()), this, SLOT(count())); 
    } 

    timer->start(intvl); 
    exec(); 
} 

void MyThread::count(void) 
{ 
    if(i >= 100 || i <= 0) 
     inc = -inc; 
    i += inc; 
    emit valueChanged(i); 
} 

當我執行的應用程序,並單擊打開按鈕,啓動主窗口:: openProject(),我得到:

QObject: Cannot create children for a parent that is in a different thread. 
(Parent is MyThread(0x5463930), parent's thread is QThread(0x3cd1f80), current thread is MyThread(0x5463930) 
MainWindow::animateLoadingScreen 
MainWindow::animateLoadingScreen 
.... 
MainWindow::animateLoadingScreen 
MainWindow::animateLoadingScreen 

(and here the controller outputs. and no MainWindow::animateLoadingScreen anymore so the widget loading screen is never animated during the opening of the project) 

那麼做我必須做什麼,我必須在MyThread類中放置什麼,如何將其信號鏈接到MainWindow以更新加載屏幕。我認爲在MainWindow中創建的widgetLoadingScreen可能存在問題,因此如果由於打開而導致MainWindow被阻塞,那麼widgetLoadingScreen不能更新,因爲它位於處理GUI的MainWindow線程中?

我讀: http://www.qtcentre.org/wiki/index.php?title=Updating_GUI_from_QThread 但是那一個,我在運行時遇到錯誤消息,這是一個我在我上面給出 QObject的代碼中使用:無法爲父母是在不同的線程創建的兒童。 (Parent是MyThread的(0x41938e0),父母的線程的QThread(0x1221f80),當前線程是MyThread的(0x41938e0)

我想,太: How to emit cross-thread signal in Qt? 但是,即使我沒有在運行時錯誤消息對於未更新的動畫也是如此

我完全失去了,我不認爲在主線程打開項目時在線程中加載屏幕是一件難事。

+0

您的帖子甚至沒有做任何事情。不用新線程就可以使用'QTimer'。繁重的處理是你需要轉移到一個新的線程,這對我來說似乎是在你的Controller類中。 – thuga

+1

這又是一個*** [我的子類的QThread做多線程,它不工作(http://codethis.wordpress.com/2011/04/04/using-qthread-without-subclassing/)***問題 – UmNyobe

+0

@thuga從我在http://www.qtcentre.org/wiki/index.php?title=Updating_GUI_from_QThread中理解的內容來看,它是使run函數執行發出信號的count函數的計時器。我不能將繁重的過程移動到新的線程,它會更加複雜,因爲加載過程會發出多個信號,...更新其他gui類。這就是爲什麼我想在另一個線程中創建一個簡單的加載控件,當我需要很長的加載過程時,我可以運行它。 – SteveTJS

回答

0

Qt有一個QSplashScreen類,您可以將它用於加載屏幕

除此之外,您遇到的問題之一是由於線程關聯(對象正在運行的線程)以及您決定從QThread繼承的事實。

在我看來,QThread有一個誤導性的名字。它更像是一個線程控制器,除非你想改變Qt處理線程的方式,你真的不應該繼承它,許多人會告訴你「You're doing it Wrong!

通過繼承QThread,你的MyThread實例運行在主線程。但是,當調用exec()時,它會啓動新線程及其所有子項(包括QTimer)的事件循環,並嘗試將其移至新線程。

解決此問題的正確方法不是從QThread繼承,而是創建一個從QObject派生並派生到新線程的工作對象。你可以閱讀關於如何'Really Truly Use QThread' here

+0

好的,我會讀到的!我首先嚐試使用splashscreen,但爲了使它動起來,我還需要另一個線程。 – SteveTJS

+0

如果每隔一段時間調用一次qApp.processEvents(),並且使用setPixamp更新屏幕,則不需要另一個線程 – TheDarkKnight

+0

如果我不使用另一個線程,問題是當加載進程阻塞或需要(非常)很長的時間(加載大圖像,創建縮略圖,...),加載屏幕將不會被更新,因爲processEvents()沒有執行,因爲進程是塊或需要.... :)循環。這就是爲什麼我想將加載屏幕放在另一個不會被加載過程阻塞的線程中。 – SteveTJS

0

第一問題 - 與QTimer

問題運行時錯誤是在void MyThread::run(void),線timer = new QTimer(this);。線程實例是在不同的(主線程)線程中創建的(並由其擁有),然後它代表。出於這個原因,你不能使用線程作爲父對象創建裏面的線程。在你的情況下,解決方案很簡單 - 在堆棧上創建定時器。

實施例:

void MyThread::run(void) 
{ 
    QTimer timer; 
    connect(&timer, SIGNAL(timeout()), this, SLOT(count())); 
    timer.start(intvl); 

    // Following method start event loop which makes sure that timer 
    // will exists. When this method ends, timer will be automatically 
    // destroyed and thread ends. 
    exec(); 
} 

第二問題 - GUI不更新

Qt的要求創建和從主應用程序線程顯示的GUI。這意味着用大操作塊阻塞整個GUI的主線程。只有兩種可能的解決方案:

  1. 將繁重工作移至其他(加載)線程。這是最好的解決方案,我相信這是值得的(我知道你寫道這會有問題)。
  2. 在繁重的操作過程中,請定期致電QCoreApplication::processEvents() - 每次調用此函數時,都會處理事件,包括調用槽,鍵和鼠標事件,GUI重繪等。您經常調用此函數, 。這很容易實現(只需在代碼中放一行),但這很難看,如果操作在加載文件時阻塞,GUI會凍結一段時間。這意味着您的GUI的響應取決於調用之間的時間間隔。 (但是,如果你過於頻繁調用它,你會減慢加載進度。)這種技術氣餒,這是最好的避免它...
+1

定時器將在調用線程中啓動。所以這就好像定時器是在mainwindow中創建的一樣,只是它增加了訪問無效對象的可能性。 – UmNyobe

+0

@UmNyobe我很抱歉,但我不明白... –

+0

令人驚訝的是,似乎'Qthread :: run()'不會在不同的線程中執行。它旨在啓動一個偶數循環(在'exec()'內部),它使用不同的線程來執行***插槽***。你在線程關係上是正確的。但是MyThread :: run中的定時器比外部沒有任何好處。 – UmNyobe

0

基礎上,我會做到這一點default example on Qthread頁:

  1. 打破大controller->openProject();,使得它更新使用的信號可變的可變
  2. 提供訪問(如statusChanged(int))。
  3. 一旦信號被觸發,就做你需要做的事情。

代碼看起來像

class Worker : public QObject 
{ 
    Q_OBJECT 
    QThread workerThread; 

private: 
    int status; // 
    bool hasworktoDo(); 
    void doSmallWork(); 

public slots: 
    void doWork() { 

     while(hasworktoDo()) 
     { 
      doSmallWork(); // fragments of controller->openProject(); 
      ++status; //previously set to 0 
      emit statusChanged(status); 
     } 
     emit resultReady(result); 
    } 


signals: 
    void resultReady(const QString &result); 
    void statusChanged(int value); 
}; 

class Controller : public QObject 
{ 
    Q_OBJECT 
    QThread workerThread; 
public: 
    Controller() { 
     Worker *worker = new Worker; 
     worker->moveToThread(&workerThread); 
     connect(workerThread, SIGNAL(statusChanged(int)), this, SLOT(updateStatus(int))); 
     connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater())); 
     connect(this, SIGNAL(operate()), worker, SLOT(doWork())); 
     connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString))); 
     workerThread.start(); 
    } 
    ~Controller() { 
     workerThread.quit(); 
     workerThread.wait(); 
    } 
public slots: 
    void handleResults(const QString &); 

    void updateStatus(int value) 
    { 
     int status = value; 
     //now you have what to use to update the ui 
    } 
signals: 
    void operate(); // 
}; 

如果你有麻煩打破了小部件的openProject操作,它只是意味着它是沒有意義的對,你是在50%的UI說...只是說Loading...並使代碼更簡單。請注意,在我的情況下,您不需要計時器,因爲工作人員會定期更新