Qt - 用第二个线程更新主窗口

时间:2013-05-11 20:09:59

标签: c++ multithreading qt

我有一个多线程的qt应用程序。当我在mainwindow.cpp中做一些进程时,我想同时从其他线程更新mainwindow.ui。

我有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);

我该怎么做?

3 个答案:

答案 0 :(得分:16)

  

但问题是,我无法达到   ana-&GT; UI-&GT; horizo​​ntalLayout_4-&GT; addWidget(标签);

将您的UI修改放在主窗口的插槽中,并将线程信号连接到该插槽,可能会起作用。我认为只有主线程可以访问Qt中的UI。因此,如果您想要GUI功能,它必须在那里,并且只能从其他线程发出信号。

好的,这是一个简单的例子。顺便说一下,你的场景并不需要扩展QThread - 所以你最好不要这样做,除非你真的需要。这就是为什么在这个例子中我将使用普通QThread而不是基于QObject的工作人员,但如果你继承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 &);
};

创建worker对象并将其移动到另一个线程,然后连接到创建标签的插槽,然后调用其newLabel方法,这只是发出requestNewLabel信号的包装器,将路径传递给图像。然后,信号将从工作对象/线程传递到主UI插槽以及图像路径参数,并将新标签添加到布局中。

由于工作者对象是在没有父项的情况下创建的,以便能够将其移动到另一个线程,因此我们还将线程销毁信号连接到工作者deleteLater()插槽。

答案 1 :(得分:4)

首先,"you're doing it wrong"。通常,您希望创建一个派生自QObject的类,并将该类移动到新的线程对象,而不是从Qthread派生您的类

现在,为了了解您的问题的细节,您无法从单独的线程直接修改主GUI线程的ui元素。您必须从第二个主题connect signal到主线程中的slot。你可以通过这个信号/插槽连接传递你需要的任何数据,但是你无法直接修改ui元素(如果你打算将应用程序的前端与后端分开,你可能根本就不想这样做) )。检查Qt的信号和插槽documentation以获取更多信息

答案 2 :(得分:2)

  

我该怎么做?

你已经得到了你应该做的事情的答案,但不是为什么,所以我要补充一个原因。

您不从其他线程修改GUI元素的原因是因为GUI元素通常不是thread-safe。这意味着如果您的主GUI线程和工作线程都更新了UI,则无法确定何时发生的事件的顺序。

对于读取数据一般来说,这有时可以很好(例如检查条件),但通常你不希望这种情况。对于写入数据,这几乎总是“随机”发生的非常非常紧张的错误的来源。

另一个答案已经说明了良好的设计原则 - 不仅限制你的GUI逻辑到一个线程,并且发出信号与它交谈以摆脱你的竞争条件问题,但它也迫使你很好地划分你的代码。然后可以将表示逻辑(显示位)和数据处理逻辑完全分离出来,这样可以更容易地维护这两者。

在这个阶段你可能会想:哎呀,这个线程业务是 farrrrrr 太多工作了!我会避免这种情况。要了解为什么这是一个坏主意,请在单个线程中实现文件复制程序,并使用简单的进度条告诉您复制的距离。在大文件上运行它。在Windows上,一段时间后,应用程序将“变白”(或在XP上我认为它变灰)并且将“无响应”。这就是字面上正在发生的事情。

GUI应用程序内部主要处理“一个大循环”处理和调度消息的变化。例如,Windows测量对这些消息的响应时间。如果消息需要很长时间才能得到响应,那么Windows会确定它已经死了,然后接管。 This is documented in GetMessage()

因此虽然看起来似乎有点工作,但Signals / Slots(一个事件驱动的模型)基本上是要走的路 - 另一种思考方式是你的线程完全可以生成“事件“也适用于UI - 例如进度更新等。