Qt在打开项目时添加加载小部件屏幕

时间:2014-06-16 09:00:06

标签: multithreading qt qthread

我创建了一个应用程序,我想在应用程序打开一个项目时添加一个加载屏幕,因为项目的加载时间很长,有时gui阻塞,所以用户可以认为发生了崩溃。 / p>

所以我尝试了QThread,阅读doc和"解决了#34;这个论坛上的例子,但无事可做,我无法使其发挥作用。

我有一个处理GUI的MainWindow类,这个类是我在main函数中创建的类:

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);
}

mainwindow.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);
}

当我执行应用程序,然后单击启动MainWindow :: 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的线程中? / p>

我看了: 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? 但是,即使我没有在运行时收到错误消息,对于未更新的动画也是如此。

我完全迷失了,在主线程打开项目时,我不认为在线程中加载屏幕很难做到这一点?!

3 个答案:

答案 0 :(得分:0)

Qt有一个QSplashScreen课程,您可以将其用于加载屏幕。

除此之外,您遇到的一个问题是由于线程关联(运行对象的线程)以及您决定从QThread继承的事实。

在我看来,QThread有一个误导性的名字。它更像是一个线程控制器,除非你想改变Qt处理线程的方式,你真的不应该继承它,许多人会告诉你“You're doing it Wrong!

通过继承QThread,您的MyThread实例正在主线程上运行。但是,当调用exec()时,它会为新线程及其所有子节点(包括QTimer)启动事件循环,并尝试将其移动到新线程。

解决这个问题的正确方法不是继承QThread,而是创建一个派生自QObject的工作对象并将其移动到新线程。您可以阅读有关如何“Really Truly Use QThread' here

的信息

答案 1 :(得分: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会冻结一段时间。这意味着GUI的响应性取决于调用之间的间隔。 (但如果你经常调用它,你会减慢加载进度。) 不鼓励使用这种技术,最好避免使用它......

答案 2 :(得分:0)

我将根据default example on Qthread页面

执行此操作
  1. 打破大controller->openProject();以便更新变量
  2. 使用信号(如statusChanged(int))提供对该变量的访问权。
  3. 触发信号后,您需要做什么。
  4. 代码看起来像

    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%毫无意义...只需说出Loading...和使代码更简单。请注意,在我的情况下,您不需要计时器,因为工作人员会定期发送更新