Qt学习(十八)—— 线程应用之画图

今天的目标是将之前学习的绘图和线程结合起来,来实现用线程绘图(……好绕)

先回顾一下创建一个子线程的流程:

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:如有错误,欢迎指正~

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
天气预报是一个非常实用的个人项目,它可以帮助我们及时了解当地的天气情况,帮助我们合理安排日常生活和外出计划。 我的qt个人项目天气预报主要包括以下功能: 1. 实时天气查询:通过调用天气API,可以实时获取当地的天气信息,包括温度、湿度、风力、空气质量等指标。用户可以根据自己的需求选择查询当天或未来几天的天气情况。 2. 城市选择和管理:用户可以通过界面上的搜索功能输入城市名字查询该城市的天气情况。同时,用户还可以通过添加和删除城市的功能,方便地管理自己关注的城市列表。 3. 天气图标和背景:界面上会显示对应的天气图标,如晴天、多云、雨天等,以直观地展示当前的天气情况。同时,根据不同的天气状况,背景图片也会相应地进行变化,增加界面的美观性和可视化效果。 4. 未来天气预测:除了显示当天的天气情况外,我的个人项目还可以提供未来几天的天气预测,帮助用户更全面地了解未来几天的天气变化趋势。 通过这个个人项目,用户可以方便地获取并查看天气信息,从而更好地做出日常生活安排。此外,用户还可以根据天气预报的变化,做出适应性的衣物搭配和外出计划,提高生活效率和便利性。 总之,我的qt个人项目天气预报能够为用户提供准确、及时的天气信息,并通过直观的界面展示,帮助用户做出合理的日常生活安排和外出计划。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值