今天的目标是将之前学习的绘图和线程结合起来,来实现用线程绘图(……好绕)
先回顾一下创建一个子线程的流程:
1)创建一个
自定义线程类
,并实现它的线程处理函数
(本质是类的成员函数)。2)在主窗口类中创建一个
自定义线程对象
,再创建一个QThread子进程对象
,然后用moveToThread()
方法将自定义线程对象移动到子线程中。3)调用
start()
方法启动子线程。4)调用
connect()
方法,当主线程触发某些信号时,调用子线程中的槽函数。5)在子线程的槽函数中,也可以向主线程发送信号。
6)调用
connect()
方法,当主线程接收到由子线程发送过来的信号,再调用主线程中的槽函数进行相应的处理。
再回顾一下绘图的流程:
1)创建一个绘图设备对象,比如
QImage
。2)创建一个
QPainter
画家对象。3)关联画家和绘图设备。
4)调用
QPainer
画家的drawXXX()
方法进行绘图,也可以对QPen
画笔、QBrush
画刷进行设置。
那么,如何将绘图和线程联系起来呢?
整个绘图过程可以看做是子线程中的数据处理,在
数据处理函数
中实现。由主线程来决定子线程的数据处理函数何时被调用。
主线程又要如何获取子线程中数据处理的结果?比如——绘制好的图片。
我们都知道信号和槽是可以带参数的,而且一对匹配的信号和槽,它们的参数必须保持一致。所以,在子线程完成数据处理之后,可以发射一个携带参数的信号,这个参数就是数据处理的结果。而主线程接收到这个信号以后,会自动调用主线程中对应的槽函数,并获取由信号传递过来的数据处理结果,主线程得到这个数据处理结果后,就可以根据实际需要来进行下一步操作。
大致理清思路就开始动手吧。想要实现的效果是:在鼠标每次点击的位置绘制一个脚印。
首先,先创建一个自定义线程类,记得它是继承自QObejct
的。
接着,在自定义线程类中定义一个用来画图的成员函数slotDrawImage()
,也就是线程处理函数。再定义一个信号signalDrawUp()
,在绘图工作完成时向主线程发送此信号,由于要将子线程绘制的图片传递给主线程,所以信号需要带参数
。
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include <QImage>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
void slotDrawImage(); //线程处理函数
signals:
void signalDrawUp(QImage img); //绘图结束信号,携带的参数是QImage对象
public slots:
};
#endif // MYTHREAD_H
先不急着实现线程处理函数,回到主线程,创建自定义线程对象和QThread子线程对象,将自定义线程对象移动到子线程中。
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "MyThread.h"
#include <QThread>
#include <QImage>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
MyThread *m_pMyThread; //自定义线程对象
QThread *m_pThread; //子线程对象
QImage m_Image; //绘图设备
};
#endif // WIDGET_H
//Widget.cpp中Widget构造函数
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(1400,850);
m_pMyThread=new MyThread; //为自定义线程对象分配空间,但不要指定父对象
m_pThread=new QThread(this); //为子线程对象分配空间,可以指定父对象
m_pMyThread->moveToThread(m_pThread); //将自定义线程对象移动到子线程中
//启动子线程
m_pThread->start();
}
要在主窗口中绘图,必须重写paintEvent()绘图事件
。既然我们的图片最终是要显示在主窗口上的,所以在paintEvent()
中,还是要定义一个QPainter画家
,并指定绘图设备为当前窗口。
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include <QWidget>
#include "MyThread.h"
#include <QThread>
#include <QImage>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void paintEvent(QPaintEvent *event); //重写绘图事件
private:
MyThread *m_pMyThread; //自定义线程对象
QThread *m_pThread; //子线程对象
QImage m_Image; //绘图设备
int m_x; //绘图起点横坐标
int m_y; //绘图起点纵坐标
signals:
void clicked(int x,int y);
};
#endif // WIDGET_H
#include "Widget.h"
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include <QPoint>
#include <QRect>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(1400,850);
m_pMyThread=new MyThread; //为自定义线程对象分配空间,但不要指定父对象
m_pThread=new QThread(this); //为子线程对象分配空间,可以指定父对象
m_pMyThread->moveToThread(m_pThread); //将自定义线程对象移动到子线程中
//初始化绘图起点
m_x=0;
m_y=0;
//启动子线程
m_pThread->start();
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter painter(this); //创建画家,指定绘图设备为主窗口
}
现在遇到了一个问题,我们想要的效果是以鼠标光标在主窗口的相对位置
为起点,绘制一个图片。但是,主窗口并没有clicked()信号。我想到的解决方法是,重写mousePressEvent()
,并且获取点击事件在主窗口中的相对位置的坐标,然后发送一个携带参数的clicked()信号
,把这个位置信息传递给子线程的槽函数,也就是线程处理函数slotDrawImage()
。
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include <QWidget>
#include "MyThread.h"
#include <QThread>
#include <QImage>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void paintEvent(QPaintEvent *event); //重写绘图事件
void mousePressEvent(QMouseEvent *event); //重写鼠标点击事件
private:
MyThread *m_pMyThread; //自定义线程对象
QThread *m_pThread; //子线程对象
QImage m_Image; //绘图设备
int m_x; //绘图起点横坐标
int m_y; //绘图起点纵坐标
signals:
void clicked(int x,int y); //主窗口的clicked信号,参数是鼠标点击事件的坐标
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include <QPoint>
#include <QRect>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(1400,850);
m_pMyThread=new MyThread; //为自定义线程对象分配空间,但不要指定父对象
m_pThread=new QThread(this); //为子线程对象分配空间,可以指定父对象
m_pMyThread->moveToThread(m_pThread); //将自定义线程对象移动到子线程中
//初始化绘图起点
m_x=0;
m_y=0;
//启动子线程
m_pThread->start();
//主线程发送clicked信号,子线程调用槽函数进行绘制
connect(this,&Widget::clicked,m_pMyThread,&MyThread::slotDrawImage);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter painter(this); //创建画家,指定绘图设备为窗口
}
void Widget::mousePressEvent(QMouseEvent *event){
this->m_x=event->x();
this->m_y=event->y();
emit clicked(m_x,m_y);
}
子线程通过槽函数得到clicked()信号
传递过来的坐标信息,就可以开始绘制图片了,绘制完毕后,同样需要发射一个signalDrawUp()信号
,告诉主线程已经画完了,并且把绘制的图片作为参数传递给主线程的槽函数:
//MyThread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#pragma execution_character_set("utf-8")
#include <QObject>
#include <QImage>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
void slotDrawImage(int x,int y); //线程处理函数
signals:
void signalDrawUp(QImage img); //绘制完毕时发射的信号
public slots:
};
#endif // MYTHREAD_H
#include "MyThread.h"
#include <QImage>
#include <QPainter>
#include <QDebug>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::slotDrawImage(int x,int y){
//新建绘图设备并指定大小
QImage img(1400,850,QImage::Format_ARGB32);
//新建画家并关联绘图设备
QPainter painter(&img);
//绘图
painter.drawImage(x,y,QImage("C:/Users/MSI-NB/Desktop/paw.png"));
//绘图完毕,向主线程发射信号,并将img作为参数
emit signalDrawUp(img);
}
主线程调用slotGetImage()槽函数
,接收由子线程绘制的图片,并调用update()方法
,以此间接调用paintEvent()绘图事件
:
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include <QWidget>
#include "MyThread.h"
#include <QThread>
#include <QImage>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void paintEvent(QPaintEvent *event); //重写绘图事件
void mousePressEvent(QMouseEvent *event); //重写鼠标点击事件
void slotGetImage(QImage img); //主窗口获取图片并更新
private:
MyThread *m_pMyThread; //自定义线程对象
QThread *m_pThread; //子线程对象
QImage m_Image; //绘图设备
int m_x; //绘图起点横坐标
int m_y; //绘图起点纵坐标
signals:
void clicked(int x,int y);
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include <QPoint>
#include <QRect>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(1400,850);
m_pMyThread=new MyThread; //为自定义线程对象分配空间,但不要指定父对象
m_pThread=new QThread(this); //为子线程对象分配空间,可以指定父对象
m_pMyThread->moveToThread(m_pThread); //将自定义线程对象移动到子线程中
//初始化绘图起点
m_x=0;
m_y=0;
//启动子线程
m_pThread->start();
//主线程发送clicked信号,子线程调用槽函数进行绘制
connect(this,&Widget::clicked,m_pMyThread,&MyThread::slotDrawImage);
//子线程绘制完毕发送drawUp信号,主线程获取图片
connect(m_pMyThread,&MyThread::signalDrawUp,this,&Widget::slotGetImage);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter painter(this); //创建画家,指定绘图设备为窗口
painter.drawImage(m_x,m_y,m_Image,m_x,m_y,150,162); //以鼠标点击的位置为左上角起点绘制图片
}
void Widget::mousePressEvent(QMouseEvent *event){
this->m_x=event->x();
this->m_y=event->y();
emit clicked(m_x,m_y);
}
void Widget::slotGetImage(QImage img){
m_Image=img;
update();
}
最后,再处理一下关闭串口时子线程的关闭和回收问题:
//Widget构造函数中
//关闭窗口时,同时关闭子线程
connect(this,&Widget::destroyed,this,&Widget::slotDealDestroyed);
//Widget.cpp
void Widget::slotDealDestroyed(){
m_pThread->quit();
m_pThread->wait();
}
实现效果:
emmm…图片是png格式的,但是不知道为啥还是带了灰色背景…哪位大佬知道原因的话还请指点一下QAQ,感激不尽!!
以下是完整代码:
//MyThread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#pragma execution_character_set("utf-8")
#include <QObject>
#include <QImage>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
void slotDrawImage(int x,int y);
signals:
void signalDrawUp(QImage img);
public slots:
};
#endif // MYTHREAD_H
//MyThread.cpp
#include "MyThread.h"
#include <QImage>
#include <QPainter>
#include <QDebug>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::slotDrawImage(int x,int y){
//新建绘图设备并指定大小
QImage img(1400,850,QImage::Format_ARGB32);
//新建画家并关联绘图设备
QPainter painter(&img);
//绘图
painter.drawImage(x,y,QImage("C:/Users/MSI-NB/Desktop/paw.png"));
//绘图完毕,向主线程发射信号,并将img作为参数返回
emit signalDrawUp(img);
}
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include <QWidget>
#include "MyThread.h"
#include <QThread>
#include <QImage>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void paintEvent(QPaintEvent *event); //重写绘图事件
void mousePressEvent(QMouseEvent *event); //重写鼠标点击事件
void slotGetImage(QImage img); //主窗口获取图片并更新
void slotDealDestroyed();
private:
MyThread *m_pMyThread; //自定义线程对象
QThread *m_pThread; //子线程对象
QImage m_Image; //绘图设备
int m_x; //绘图起点横坐标
int m_y; //绘图起点纵坐标
signals:
void clicked(int x,int y);
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include <QPoint>
#include <QRect>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(1400,850);
m_pMyThread=new MyThread; //为自定义线程对象分配空间,但不要指定父对象
m_pThread=new QThread(this); //为子线程对象分配空间,可以指定父对象
m_pMyThread->moveToThread(m_pThread); //将自定义线程对象移动到子线程中
//初始化绘图起点
m_x=0;
m_y=0;
//启动子线程
m_pThread->start();
//主线程发送clicked信号,子线程调用槽函数进行绘制
connect(this,&Widget::clicked,m_pMyThread,&MyThread::slotDrawImage);
//子线程绘制完毕发送drawUp信号,主线程获取图片
connect(m_pMyThread,&MyThread::signalDrawUp,this,&Widget::slotGetImage);
//关闭窗口时,同时关闭子线程
connect(this,&Widget::destroyed,this,&Widget::slotDealDestroyed);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter painter(this); //创建画家,指定绘图设备为窗口
painter.drawImage(m_x,m_y,m_Image,m_x,m_y,150,162); //以鼠标点击的位置为左上角起点绘制图片
}
void Widget::mousePressEvent(QMouseEvent *event){
this->m_x=event->x();
this->m_y=event->y();
emit clicked(m_x,m_y); //发送clicked信号
}
void Widget::slotGetImage(QImage img){
m_Image=img; //获取子线程传过来的图片
update(); //间接调用绘图事件
}
void Widget::slotDealDestroyed(){
m_pThread->quit(); //关闭子线程
m_pThread->wait(); //回收资源
}
//main.cpp
#include "Widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
P.S:如有错误,欢迎指正~