2013-05-11 114 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()。因此,虽然它看起来像一个相当多的工作,信号/槽(一个事件驱动模型)基本上是要走的路 - 另一种方式想到这一点,它是完全可以接受你的线程产生用户界面的“事件” - 比如进度更新等。