往期回顾:
【QT入门】 Qt自定义信号后跨线程发送信号
由于Qt的子线程是无法直接修改ui,需要发送信号到ui线程进行修改,所以会跨线程发送信号。
一、思路
思路基本一致,子线程发送一个信号,父线程接收信号并执行槽函数,把子线程传递的数据展示在父线程ui上。
二、步骤
1.如何创建子线程
右键单击项目,选择Add New->C++>C++ Class即可
ChildThread是我们自己取的子线程名字
下面的基类因为没有合适的基类,我们选择Custom,继承QThread类
这里注意,创建以后由于是自己填的继承自QThread类,它不一定包含了相应的头文件,需要我们自己补上。
2、添加Q_OBJECT宏
自己创建的子线程是不包含Q_OBJECT宏的,如果需要用到信号槽,需要自己补上Q_OBJECT ,一般建议大家不管用不用,创建后就都补上。
3、子线程重写run方法
子线程继承父线程之后需要重写父线程的run方法,关于线程这一块知识点,后续会有更加详细的讲解,比如:子线程重写的run方法在子线程里,但是其构造函数却是在父线程里等
void ChildThread::run()
{
//打印当前线程的线程号
qDebug()<<"child thread id= "<<QThread::currentThreadId();
Score s;
s.name="zhangsan";
s.age=18;
s.id=001;
emit sig_sendScore(s);
}
注:
当我们分不清代码运行在哪个线程的时候,可以用QThread::currentThreadId();方法打印当前线程的线程号来判断。
这里数据方面用了一个结构体来写数据
struct Score
{
string name;
int age;
int id;
};
4、启动子线程
父线程按钮点击的槽函数里创建子线程,接受子线程的信号并启动子线程
void Widget::on_btnOpen_clicked()
{
ChildThread *ch =new ChildThread();
connect(ch,&ChildThread::sig_sendScore,[=](Score s){
string info="name="+s.name+" age="+to_string(s.age)+" id="+to_string(s.id);
ui->lineEdit->setText(QString::fromStdString(info));
});
qDebug()<<"widget thread id= "<<QThread::currentThreadId();
ch->start();
}
针对代码看几个注意点:
|-------------------------------------------------------------------------------|
| 1、要在Qt用c++的string类型,一个加头文件,二个加命名空间 |
| 2、age和id这种int类型要转成string,用一个to_string(),复习,int转Qstring?用QString::number() |
| 3、setText放的是QString类型的,这里info是string类型,所以需要转QString,用QString::fromStdString() |
但是,由于ChildThread的ch对象的槽函数sig_sendScore连接到了lambda表达式中,lambda表达式可能在ChildThread的线程中执行。这导致槽函数执行时在ChildThread的线程中运行,而不是在主线程中。
为了让槽函数在父线程执行,要么不用lambda表达式,改用槽函数,要么改写lambda表达式
4.1 改用槽函数
void Widget::on_btnOpen_clicked()
{
ChildThread *ch =new ChildThread();
connect(ch,&ChildThread::sig_sendScore,this,&Widget::showIofo);
qDebug()<<"ui01 thread id= "<<QThread::currentThreadId();
ch->start();
}
4.2 改写lambda表达式
void Widget::on_btnOpen_clicked()
{
ChildThread *ch =new ChildThread();
connect(ch, &ChildThread::sig_sendScore, this, [=](Score s){
string info = "name=" + s.name + " age=" + to_string(s.age) + " id=" + to_string(s.id);
ui->lineEdit->setText(QString::fromStdString(info));
qDebug()<<"slots thread id= "<<QThread::currentThreadId();
}, Qt::QueuedConnection);
qDebug()<<"ui01 thread id= "<<QThread::currentThreadId();
ch->start();
}
使用Qt::QueuedConnection连接信号和槽,这样信号会被投递到接收者所在的线程中执行。可以确保槽函数在接收者所在的线程中执行,从而解决可能的线程问题。
三、报错
当成功在父线程执行后,报了一个错误:
QObject::connect: Cannot queue arguments of type 'Score'
(Make sure 'Score' is registered using qRegisterMetaType().)
这是告诉我们Score是一个非基础类型参数,需要进行注册,在子线程的构造函数实现里注册即可
ChildThread::ChildThread()
{
//非基础类型参数注册
qRegisterMetaType<Score>("Score");
}
四、最终代码
最后,附上最终代码,以便供大家参考
1、widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include "childthread.h"
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_btnOpen_clicked();
void showIofo(Score s);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
2、widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btnOpen_clicked()
{
ChildThread *ch =new ChildThread();
//ch对象的槽函数sig_sendScore连接到了lambda表达式中,lambda表达式可能在ChildThread的线程中执行。
//这可能导致槽函数执行时在ChildThread的线程中运行,而不是在主线程中。
// connect(ch,&ChildThread::sig_sendScore,[=](Score s){
// string info="name="+s.name+" age="+to_string(s.age)+" id="+to_string(s.id);
// ui->lineEdit->setText(QString::fromStdString(info));
// qDebug()<<"slots thread id= "<<QThread::currentThreadId();
// });
// connect(ch, &ChildThread::sig_sendScore, this, [=](Score s){
// string info = "name=" + s.name + " age=" + to_string(s.age) + " id=" + to_string(s.id);
// ui->lineEdit->setText(QString::fromStdString(info));
// qDebug()<<"slots thread id= "<<QThread::currentThreadId();
// }, Qt::QueuedConnection);
//
connect(ch,&ChildThread::sig_sendScore,this,&Widget::showIofo);
qDebug()<<"ui01 thread id= "<<QThread::currentThreadId();
ch->start();
}
void Widget::showIofo(Score s)
{
qDebug()<<"ui02 thread id= "<<QThread::currentThreadId();
string info="name="+s.name+" age="+to_string(s.age)+" id="+to_string(s.id);
//setText放的是QString类型的,这里info是string类型,所以需要转QString
ui->lineEdit->setText(QString::fromStdString(info));
}
3、childthread.h
#ifndef CHILDTHREAD_H
#define CHILDTHREAD_H
#include <QThread>
#include <string>
using namespace std;
//定义一个结构体函数
struct Score
{
string name;
int age;
int id;
};
class ChildThread : public QThread
{
Q_OBJECT
public:
ChildThread();
protected:
void run() override ;
signals:
void sig_sendScore(Score s);
};
#endif // CHILDTHREAD_H
4、childthread.cpp
#include "childthread.h"
#include <QDebug>
ChildThread::ChildThread()
{
//非基础类型参数注册
qRegisterMetaType<Score>("Score");
}
void ChildThread::run()
{
//打印当前线程的线程号
qDebug()<<"child thread id= "<<QThread::currentThreadId();
Score s;
s.name="zhangsan";
s.age=18;
s.id=001;
emit sig_sendScore(s);
}
都看到这里了,点个赞再走呗朋友~
加油吧,预祝大家变得更强!