从后台工作线程修改Qt GUI

时间:2013-01-27 09:08:43

标签: linux multithreading qt user-interface

我在Qt工作,当我按下按钮GO时,我需要不断地将包发送到网络并使用我收到的信息修改界面。

问题是按钮中有一个while(1),因此按钮永远不会完成,因此界面永远不会更新。我想在按钮中创建一个线程并将while(){}代码放在那里。

我的问题是如何从线程修改界面? (例如,如何从线程中修改textBox?

4 个答案:

答案 0 :(得分:35)

关于Qt的重要一点是,必须仅从GUI线程(即主线程)使用Qt GUI。

这就是为什么正确的方法是从工作者通知主线程,主线程中的代码实际上会更新文本框,进度条或其他内容。

我认为,最好的方法是使用QThread而不是posix线程,并使用Qt 信号进行线程之间的通信。这将是您的工作人员,thread_func的替换者:

class WorkerThread : public QThread {
    void run() {
        while(1) {
             // ... hard work
             // Now want to notify main thread:
             emit progressChanged("Some info");
        }
    }
    // Define signal:
    signals:
    void progressChanged(QString info);
};

在您的小部件中,使用与.h中的信号相同的原型定义插槽

class MyWidget : public QWidget {
    // Your gui code

    // Define slot:
    public slots:
    void onProgressChanged(QString info);
};

在.cpp中实现此功能:

void MyWidget::onProgressChanged(QString info) {
    // Processing code
    textBox->setText("Latest info: " + info);
}

现在在你想要产生一个线程的地方(按钮点击):

void MyWidget::startWorkInAThread() {
    // Create an instance of your woker
    WorkerThread *workerThread = new WorkerThread;
    // Connect our signal and slot
    connect(workerThread, SIGNAL(progressChanged(QString)),
                          SLOT(onProgressChanged(QString)));
    // Setup callback for cleanup when it finishes
    connect(workerThread, SIGNAL(finished()),
            workerThread, SLOT(deleteLater()));
    // Run, Forest, run!
    workerThread->start(); // This invokes WorkerThread::run in a new thread
}

连接信号和插槽后,在工作线程中用emit progressChanged(...)发出插槽将向主线程发送消息,主线程将调用与该信号相连的插槽onProgressChanged

P.S。我还没有测试过代码,所以如果我错了,可以随意建议编辑

答案 1 :(得分:0)

你可以使用invokeMethod()或信号和插槽机制,基本上有很多例子,比如如何发出信号以及如何在SLOT中接收信号。但是,InvokeMethod看起来很有趣。

下面是示例,其中显示了如何从线程更改标签的文本:

<强> // file1.cpp

QObject *obj = NULL; //global 
QLabel *label = new QLabel("test");
obj = label;   //Keep this as global and assign this once in constructor.

接下来在您的WorkerThread中,您可以执行以下操作:

// file2.cpp(即线程)

extern QObject *obj;
void workerThread::run()
{
     for(int i = 0; i<10 ;i++
     {
         QMetaObject::invokeMethod(obj, "setText",
                                Q_ARG(QString,QString::number(i)));
     }
     emit finished();
}

答案 2 :(得分:0)

所以机制是你不能从线程内部修改小部件,否则应用程序将崩溃并出现如下错误:

QObject::connect: Cannot queue arguments of type 'QTextBlock'
(Make sure 'QTextBlock' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Segmentation fault

要解决这个问题,您需要将线程工作封装在一个类中,例如:

class RunThread:public QThread{
  Q_OBJECT
 public:
  void run();

 signals:
  void resultReady(QString Input);
};

其中 run() 包含您想做的所有工作。

在您的父类中,您将拥有一个生成数据的调用函数和一个 QT 小部件更新函数:

class DevTab:public QWidget{
public:
  void ThreadedRunCommand();
  void DisplayData(QString Input);
...
}

然后调用线程,你将连接一些插槽,这个

void DevTab::ThreadedRunCommand(){
  RunThread *workerThread = new RunThread();
  connect(workerThread, &RunThread::resultReady, this, &DevTab::UpdateScreen);
  connect(workerThread, &RunThread::finished, workerThread, &QObject::deleteLater);
  workerThread->start();  
}

连接函数有 4 个参数,参数 1 是原因类,参数 2 是该类中的信号。参数3为回调函数类,参数4为类内回调函数。

然后你的子线程中有一个函数来生成数据:

void RunThread::run(){
  QString Output="Hello world";
  while(1){
    emit resultReady(Output);
    sleep(5);
  }
}

然后你会在你的父函数中有一个回调来更新小部件:

void DevTab::UpdateScreen(QString Input){
  DevTab::OutputLogs->append(Input);
}

然后当你运行它时,每次在线程中调用发射宏时,父级中的小部件都会更新。如果连接函数配置正确,它会自动获取发出的参数,并将其存储到回调函数的输入参数中。

这是如何工作的:

  1. 我们初始化类
  2. 我们设置了槽来处理线程完成时发生的情况以及如何处理“返回的”又名emitted 数据,因为我们无法以通常的方式从线程返回数据
  3. 然后我们使用 ->start() 调用(硬编码到 QThread 中)运行线程,QT 在类中查找硬编码名称 .run() 成员函数
  4. 每次在子线程中调用 emit resultReady 宏时,它都会将 QString 数据存储到线程之间陷入困境的某个共享数据区域中
  5. QT 检测到 resultReady 已触发,并通知您的函数 UpdateScreen(QString) 接受从 run() 发出的 QString 作为父线程中的实际函数参数。
  6. 每次触发emit关键字时都会重复。

本质上,connect() 函数是子线程和父线程之间的接口,以便数据可以来回传输。

注意: resultReady() 不需要定义。把它想象成一个存在于 QT 内部的宏。

答案 3 :(得分:-4)

你启动线程传递一些指向线程函数的指针(在posix中,线程函数有签名 void *(thread_func)(void *),在windows下也是如此) - 你是完全自由的将指针发送到您自己的数据(struct或者某些东西)并从线程函数中使用它(将指针转换为正确的类型)。好吧,内存管理应该是不存在的(所以你既不会泄漏内存也不会从线程中释放已经释放的内存),但这是一个不同的问题