1. Qt 多线程概述
-  Qt 默认只有一个主线程,该线程既要处理窗口移动,又要处理各种运算。 
-  当要处理的运算比较复杂时,窗口就无法移动,所以在处理程序时在很多情况下都需要使用多线程来完成。 
示例:移动窗口和复杂循环
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this);
  
  connect(ui->startBtn, &QPushButton::clicked, [=](){
  	// 模拟复杂运算
    for (int i = 1; i <= 10000000; i++)
    {
      ui->lcdNumber->display(QString::number(i));
    }
  });
}2. QThread
QThread 类用来创建子线程

2.1 用法-1
方式: 单独定义线程类,线程类完成全部的业务逻辑,主线程只负责启动子线程和退出子线程
使用步骤:
-  创建一个类(MyThread),并继承 QThread 
-  QThread 中有一个 protected 级别的 virtual void run(), 必须在该函数中实现线程业务逻辑 
-  启动线程必须使用 start() 槽函数 
-  在 MyThread 中可以定义线程执行完成信号(isDone), 在 run函数结束前发射isDone信号,来处理线程执行完毕的逻辑 
示例:
1.创建一个类,并继承 QThread
class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
protected:
    // 线程处理函数,不能直接调用,需要使用start来启动线程
    void run();
signals:
    // 自定义信号,当线程执行完毕时可以发射该信号,告诉系统线程已执行完毕
    void isDone();
public slots:
};2.在 run 函数中实现业务逻辑
void MyThread::run()
{
  // 模拟复杂业务逻辑
  for (int i = 0; i < 10000000; i++)
  {
    qDebug() << i;
  }
  
  // 线程结束时发送 isDone 信号
  emit this->isDone();
}3.在主窗口实例化 MyThread 类,并调用 start方法,来启动线程
4.链接myThread 对象 和 isFinished 信号
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this);
  myThread = new MyThread(this);
  connect(ui->startBtn, &QPushButton::clicked, [=](){
    // 通过 start 来启动线程,不能直接调用 run
    myThread->start();
  });
  // 当子线程结束时发送 isDone 信号,连接信号和槽 可以处理子线程结束之后的事情
  connect(myThread, &MyThread::isDone, [=](){
    qDebug() << "线程结束";
    myThread->quit();
  });
}2.2 用法-2
方式:主窗口与线程类配合完成业务逻辑。
-  子类提供线程能力, 
-  主窗口利用子类提供的线程能力进行业务处理。 同时还需要开启和关闭子线程 
示例: 计时器(线程版)
实现思路: 子类每秒向主窗口发送一个信号,主窗口接收到信号后进行累加操作,并将累加结果显示到窗口中
// mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
void sendMsg();
signals:
    void mySignal();
public slots:
};
#endif // MYTHREAD_H
// mythread.c
#include "mythread.h"
#include <QThread>
//核心功能: 没个一段时间主动向主窗口发送一个信号
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::sendMsg()
{
    while (true) {
        QThread::sleep(1);
        emit this->mySignal();
    }
}
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "mythread.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
signals:
    void startSibThread();
private:
    Ui::Widget *ui;
    MyThread *myThread;
    int num;
};
#endif // WIDGET_H
//widget.c
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QThread>
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->myThread = new MyThread;
     QThread *thread  =  new QThread(this);
     this->myThread->moveToThread(thread);
     connect(this->myThread,&MyThread::mySignal, [this](){
        this->num++;
         ui->lcdNumber->display(this->num);
    });
     connect(this, &Widget::startSibThread,this->myThread, &MyThread::sendMsg);
    connect(ui->pushButton,&QPushButton::clicked,[=](){
        thread->start();
        emit this->startSibThread();
    });
}
Widget::~Widget()
{
    delete ui;
}
实现步骤:
1.创建子类(MyThread),继承于 QObject。
==在该类中设置一个能够每秒钟发送一次信号的函数, 功能就是提供线程能力==
2.在主窗口中引入MyThread,并将其对象放在线程(QTread)中进行调用 ( 注意:此步并没有启动线程)
3.主窗口中接收信号,在匹配的槽函数中进行业务处理
4.点击按钮时启动线程 ( 使用该方法启动线程时,必须使用信号和槽的方式 )
5.关闭定时器: ① 结束线程 ② 结束 while 循环
1.创建子类(MyThread),继承于 QObject (在该类中设置一个能够每秒钟发送一次信号的函数)
class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    // 线程处理函数
    void dealThread();
signals:
    // 提供给主窗口的信号
    void mySignal();
public slots:
};void MyThread::dealThread()
{
    while (1)
    {
        QThread::sleep(1);
        emit this->mySignal();
        qDebug() << "子线程号:" << QThread::currentThread();
    }
}2.在主窗口中引入MyThread,并将其对象放在线程中进行调用
3.主窗口中接收信号,在匹配的槽函数中进行业务处理
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this);
  // 实例化自定义的线程,但是不能指定父对象
  myThread = new MyThread;
  // 创建子线程
  qThread = new QThread(this);
  // 将自定义的线程加入到子线程中, 就是将自定义线程移动到系统提供的线程对象中
  // 如果实例化 MyThread 时指定了父对象就不能移动了
  myThread->moveToThread(qThread);
  // 处理线程发射的信号
  connect(myThread, &MyThread::mySignal, [=](){
    static int i = 0;
    i++;
    ui->lcdNumber->display(QString::number(i));
  });
}4.点击按钮时启动程序
该方式启动线程时需要调用 start() 方法
同时发送自定义信号 startSubThread
void Widget::on_startBtn_clicked()
{
    // 启动线程
    qThread->start();
    emit this->startSubThread();
}Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    ......
    myThread->moveToThread(qThread);
    // 处理线程发射的信号
    connect(myThread, &MyThread::mySignal, [=](){
        static int i = 0;
        i++;
        ui->lcdNumber->display(QString::number(i));
    });
    qDebug() << "主线程号:" << QThread::currentThread();
    // 在 startSubThread 信号中调用子类提供的信号
    connect(this, &Widget::startSubThread, myThread, &MyThread::dealThread);
}5.关闭定时器:① 结束线程 ② 结束 while 循环
在 MyThread 中设置 isStop 属性,用来控制线程和循环的开启或者关闭
class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    // 线程处理函数
    void dealThread();
    // 修改isStop状态
    void setIsStop(bool flag);
signals:
    // 提供给主窗口的信号
    void mySignal();
public slots:
private:
  	// 是否关闭线程的标志   true 为关闭, false 为不关闭
    bool isStop = false;
};void MyThread::setIsStop(bool flag)
{
    this->isStop = flag;
}
void Widget::on_stopBtn_clicked()
{
    myThread->setIsStop(true);
    qThread->quit();
    qThread->wait();
}
用法小结:
-  创建 MyThread 类(继承QObject即可) 
① 提供一个信号(void mySignal())
② 定义了一个方法(void dealThread())
核心功能:使用死循环,每隔一段时间之后就发射一个信号 (角色:监工)
-  在窗口类中(widget) 
① 实例化 MyThread 得到 myThread对象
② 监听 mySignal 信号,一旦监听到信号之后就去实现业务的主体功能(i++,lcd->display(i))
③ 实例化 QThread 得到 qThread对象,再将 myThread转移到qThread中,这个时候就有了线程的功能
④ 在开始按钮上绑定信号和槽,在槽函数中调用 qThread->start() 来启动线程
⑤ 调用 MyThread::dealThread 方法,必须要在 Widget 类中使用自定义信号(startSubThread)的方式,并在其处理信号的槽函数位置调用 MyThread::dealThread 方法
2.3 轮播图
绘图基础回顾:
-  绘制图片要在 void paintEvent(QPaintEvent *event) 事件中 
-  使用到的类 QPainter 、 QPixmap 
-  窗口中的图片需要进行重绘时,需要使用 QWidget 的 update 方法 
第一步: 通过点击信号切换图片
核心思路: 将图片资源地址保存在一个 QVector 中,通过索引号来访问; 切换到下一张图片就是 索引号自增1 ,再调用 update() 方法重绘图片
① 使用数组保存图片资源地址
② 使用索引来控制显示哪张图片
③ 在 paintEvent 事件中绘制图片
④ 点击下一张按钮时对索引号进行自增1,再重绘图片
class Widget : public QWidget
{
  Q_OBJECT
public:
  explicit Widget(QWidget *parent = nullptr);
  ~Widget();
  // 绘图事件
  void paintEvent(QPaintEvent *);
private:
  Ui::Widget *ui;
	// imgPaths 用来保存图片资源路径
  QVector<QString> imgPaths;
  // index 用来控制显示 imPaths 中的那张图片
  int index = 0;
};Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this);
  this->resize(600, 600);
  // 将数组保存在一个 QVector 中
  imgPaths = {
    ":/images/1.jpg", ":/images/2.jpg", ":/images/3.jpg", ":/images/4.jpg"
  };
  // 点击下一张按钮时对索引号自增1, 注意进行边界检测
  connect(ui->nextBtn, &QPushButton::clicked, [=](){
    this->index = ++this->index == 4 ? 0 : this->index;
    // 重绘图片
    this->update();
  });
}
void Widget::paintEvent(QPaintEvent *)
{
  // 根据索引号绘制图片
  QPainter painter(this);
  QPixmap pix;
  pix.load(this->imgPaths[this->index]);
  painter.drawPixmap(QRect(0, 0, 200, 200), pix);
}第二步: 使用线程替换 next 按钮
核心思路:创建 MyThread 类,每三秒向主窗口发送一个信号,主窗口每次接收到该信号时自动对索引号自增1,接着在重绘图片
-  创建 MyThread 类,每3秒向窗口类 
-  主窗口监听到MyThread发送的信号后,对索引号进行自增1,接着在重绘图片 
-  程序启动时,开启子线程 
1.创建MyThread 类,每3秒向窗口类
class MyThread : public QObject
{
    Q_OBJECT
public:
  explicit MyThread(QObject *parent = nullptr);
  // 定义线程函数
  void dealThread();
signals:
  // 自定义信号,每三秒向主窗口发送一次信号
  void mySignal();
};void MyThread::dealThread()
{
  // 每 3 秒向窗口发送一次信号
  while (1)
  {
    QThread::sleep(3);
    emit this->mySignal();
  }
}2.主窗口监听到MyThread发送的信号后,对索引号进行自增1,接着在重绘图片
class Widget : public QWidget
{
    Q_OBJECT
public:
    ...
signals:
    // 启动子线程的信号
    void startSubThread();
private:
    ...
    MyThread *myThread;
    QThread *qThread;
};// 实例化自定义类 和 线程类,并将自定义类添加到线程中
myThread = new MyThread;
qThread = new QThread(this);
myThread->moveToThread(qThread);
// 通过线程调整索引号
connect(myThread, &MyThread::mySignal, [=](){
  this->index = ++this->index == 4 ? 0 : this->index;
  this->update();
});3.程序启动时,开启子线程
// 启动子线程就直接调用 dealThread 槽函数
connect(this, &Widget::startSubThread, myThread, &MyThread::dealThread);
// 启动子线程
qThread->start();
emit this->startSubThread();2.4 轮播图实现代码(全)
//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    void sendMsg();
    void setIsRun(bool flag);
signals:
    void mySignal();
public slots:
private:
    //true 循环发射信号,false 停止循环发送信号
    bool isRun = true;
};
#endif // MYTHREAD_H
//mythread.c
#include "mythread.h"
#include <QThread>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
 void MyThread:: sendMsg()
 {
     while(this->isRun)
     {
         QThread::sleep(3);
         emit this->mySignal();
     }
 }
 void MyThread::setIsRun(bool flag)
{
  this->isRun = flag;
 }
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QVector>
#include "mythread.h"
#include <QThread>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
    void enterEvent(QEvent *);
    void leaveEvent(QEvent *);
private slots:
    void on_pushButton_2_clicked();
    void on_pushButton_clicked();
signals:
    void startSubThread();
private:
    Ui::Widget *ui;
    MyThread *myThread;
    QThread *qThread;
    QVector<QString>imgList;
    int index = 0;
};
#endif // WIDGET_H
//widget.c
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->imgList = {":/images/ashe.png",":/images/jax.png",":/images/vn.png"};
    ui->label->setPixmap(QPixmap(this->imgList[this->index]));
    ui->label->setScaledContents(true);
    //1.准备线程
    this->myThread = new MyThread;
    this->qThread = new QThread;
    this->myThread->moveToThread(this->qThread);
    connect(this,&Widget::startSubThread,this->myThread,&MyThread::sendMsg);
    //3.处理监工提供信号
    connect(this->myThread,&MyThread::mySignal,[this](){
        this->index++;
        if (this->index == this->imgList.size())
        {
            this->index = 0;
        }
        ui->label->setPixmap(QPixmap(this->imgList[this->index]));
        });
    //2. 启动子线程
    this->qThread->start();
    emit this->startSubThread();
}
Widget::~Widget()
{
    delete ui;
}
void Widget::on_pushButton_2_clicked()
{
    this->index++;
    if (this->index == this->imgList.size())
    {
        this->index = 0;
    }
    ui->label->setPixmap(QPixmap(this->imgList[this->index]));
}
void Widget::on_pushButton_clicked()
{
    this->index--;
    if (this->index == -1)
    {
        this->index = this->imgList.size() - 1;
    }
    ui->label->setPixmap(QPixmap(this->imgList[this->index]));
}
//鼠标进入停止线程
void Widget::enterEvent(QEvent *)
{
    this->qThread->quit();
    this->myThread->setIsRun(false);
}
//鼠标离开启动线程
void Widget::leaveEvent(QEvent *)
{
    this->myThread->setIsRun(true);
    this->qThread->start();
    emit this->startSubThread();
}



















