Qt系统学习第三章 Qt消息机制和事件&绘图和绘图设备 (事件,鼠标事件, 定时器, 绘画)

鼠标事件:

  ui界面有个label, 当鼠标进入或移出, 点击或松开, 都是事件函数. 不过这些事件函数功能单一, 所以需要重写事件函数来完成复杂功能;

  上述这些事件函数 是 protected 中的虚函数(virtual void xxx()), 所以重写虚函数就重写了事件函数. 重写虚函数: 继承父类 & 重写子类; 

  鼠标移动事件, 要让label感受到, 所以要写一个子类继承QLabel, 然后重写事件. 

  1. 给项目 "添加新文件" , C++ -> C++ Class模板(不需要界面, 所以不需要创建Qt设计师界面类) -> Class name: MyLabel, Base class : QWidget(没有QLabel类, 所以选择所有控件的父类 QWidget) ;

  2. 在项目中, 先改父类; 将mylabel.h中 #include <QWidget>  改为 #include <QLabel> ,  class MyLabel : public QWidget  改为 class MyLabel : public QLabel

                mylabel.cpp中 MyLabel::MyLabel(QWidget *parent) : QWidget(parent)  改为  MyLabel::MyLabel(QWidget *parent) : QLabel(parent) 

  3. 回到widget.ui界面, 将其中的label(此时属于QLabel类)提升为MyLabel类来产生关联. 鼠标右键label -> 提升为... -> 基类名称: QLabel 提升的类名称: MyLabel -> 全局包含 -> 添加 -> 提升; 此时的label的类属于MyLabel; 

 

mylabel.h: (重写虚函数的声明) (这些都是回调函数)

复制代码
protected:
    //鼠标进入
    void enterEvent(QEvent *);
    //鼠标离开
    void leaveEvent(QEvent *);
    //鼠标按下
    void mousePressEvent(QMouseEvent *ev);
    //鼠标释放
    void mouseReleaseEvent(QMouseEvent *ev);
    //鼠标移动
    void mouseMoveEvent(QMouseEvent *ev);
    //定时器
    void timerEvent(QTimerEvent *);
复制代码

 

mylabel.cpp (重写虚函数的实现)

复制代码
//鼠标进入
void MyLabel::enterEvent(QEvent *)
{
    this->setText("进来了");
}

//鼠标进入和离开都是在触碰到边界的一瞬间完成的,后续若状态不发生变化则不再触发

//鼠标离开
void MyLabel::leaveEvent(QEvent *)
{
    this->setText("出去了");
}
复制代码

当鼠标进入MyLabel:; 当鼠标离开MyLabel:;

 

复制代码
//鼠标按下
void MyLabel::mousePressEvent(QMouseEvent *ev)
{
//    QString str = "123";
//    int number = str.toInt();
//    int number = 123;
//    str = QString::number(number);        //Qt可以通过QString的成员函数直接进行类型转换

//    QString str;
//    str.sprintf("%d, %s", 123, "aaa");      //字符串拼接
    QString str = QString("%3, %2, %1").arg(10).arg("123").arg('d');     //字符串拼接
    //%1, %2, %3 -- 占位符对应的第一二三个arg()
    setText(str);
}
复制代码

当鼠标在MyLabel中按下:;

 

复制代码
//鼠标按下
void MyLabel::mousePressEvent(QMouseEvent *ev)
{
    QString btn;
    if(ev->button() == Qt::LeftButton)
    {
        btn = "鼠标左键";
    }
    else if(ev->button() == Qt::RightButton)
    {
        btn = "鼠标右键";
    }
    else if(ev->button() == Qt::MidButton)
    {
        btn = "鼠标中键";
    }
    QString str = QString("MousePree[%3]:(%1, %2)").arg(ev->x()).arg(ev->y()).arg(btn);
    setText(str);
}
复制代码

当鼠标左键在MyLabel中按下:,当右键按下:;

 

复制代码
//鼠标释放
void MyLabel::mouseReleaseEvent(QMouseEvent *ev)
{
    QString btn;
    if(ev->button() == Qt::LeftButton)
    {
        btn = "鼠标左键";
    }
    else if(ev->button() == Qt::RightButton)
    {
        btn = "鼠标右键";
    }
    else if(ev->button() == Qt::MidButton)
    {
        btn = "鼠标中键";
    }
    QString str = QString("MouseRelease[%3]:(%1, %2)").arg(ev->x()).arg(ev->y()).arg(btn);
    setText(str);
}
复制代码

当鼠标左键在MyLabel中释放:, 当鼠标右键在MyLabel中释放;

 

复制代码
//鼠标移动
void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
    QString btn;
    //LeftButton的Value值为:  0x0000 0001 -(HEX)> 0x0000 0001
    //RightButton的Value值为: 0x0000 0002 -(HEX)> 0x0000 0010
    //MidButton的Value值为:   0x0000 0004 -(HEX)> 0x0000 0100
    //--------------------------------------------------------
    //ev->buttons()的值为上面三者的OR OR(按位异或)-> 0x0000 0111

    if(ev->buttons() & Qt::LeftButton)          //0x0000 0111 & 0x0000 0001 = 1    &:与  同1则1,同0则0,不同为0
    {
        btn = "鼠标左键";
    }
    else if(ev->buttons() & Qt::RightButton)    //0x0000 0111 & 0x0000 0010 = 2
    {
        btn = "鼠标右键";
    }
    else if(ev->buttons() & Qt::MidButton)      //0x0000 0111 & 0x0000 0100 = 4
    {
        btn = "鼠标中键";
    }
    QString str = QString("MouseMove[%3]:(%1, %2)").arg(ev->x()).arg(ev->y()).arg(btn); //按下鼠标按键移动时,不会显示此时按下的是哪个键,因为button()只能判断按下的一瞬间,此时应使用buttons()来记录持续鼠标状态
    setText(str);
}
复制代码

当按下鼠标左键移动时:; 当按下鼠标右键移动时:;

 

 QWidget默认不追踪鼠标事件, 要想让窗口追踪鼠标事件,要在mylabel.cpp中添加

//QWidget 默认是不追踪鼠标事件的
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
    //设置窗口追踪鼠标键
    this->setMouseTracking(true);
}

此时, 将鼠标放入MyLabel框中移动时, 不用按下鼠标键, 就可以看到鼠标的坐标;

 

 

 

定时器:  两种写法

第一种:

  在mylabel.h中, 

  private:
    int Id;    //代表的是定时器的id序号.这个id可能在其他函数中也会使用,故写在头文件的成员变量中
    int Id1;

  在mylabel.cpp中, 要包含头文件 #include <QTimerEvent>

  构造函数:

MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
    //窗口构造完成后启动定时器, 故将启动定时器写在构造函数中
    //参数1: 触发定时器的时间,单位:ms  参数2使用默认值
    //返回值: int类型,代表的是定时器的id序号.这个id可能在其他函数中也会使用,故写在头文件的成员变量中
    Id = startTimer(2000);
    Id1 = startTimer(3000);   //第二个定时器
}

  重写函数:

复制代码
//定时器 每触发一次定时器, 进入该函数
void MyLabel::timerEvent(QTimerEvent *e)
{
    QString str;
    if(e->timerId() == Id)
    {
        static int num = -100;     //定义静态局部变量是防止定时器触发后, num发生改变
        str = QString("%1: %2").arg("Time out: ").arg(num++);
        if(num >= 100)
        {
            //关闭定时器
            killTimer(Id);
        }
    }
    else if(e->timerId() == Id1)
    {
        static int num1 = 10000;     //第二个定时器
        str = QString("%1: %2").arg("sweet: ").arg(num1++);
        if(num1 >= 10000+1000)
        {
            //关闭定时器
            killTimer(Id1);
        }
    }
    setText(str);
}
复制代码

两个定时器:

 

第二种:

  在mylabel.cpp中, 包含头文件 #include <QTimer>, 然后在构造函数中创建一个QTimer对象

  构造函数:

复制代码
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
    QTimer * timer = new QTimer(this);
    QTimer * timer1 = new QTimer(this);   //第二个定时器 
    timer->start(2000);      //每隔2000ms触发一次
    timer1->start(3000);
    connect(timer, &QTimer::timeout, this, [=]()
    {
        static int num = 0;
        this->setText(QString::number(num++));
        if(num >= 100)
        {
            timer->stop();
        }
    });
    connect(timer1, &QTimer::timeout, this, [=]()
    {
        static int num = 0;
        this->setText(QString::number(num++));
        if(num >= 100)
        {
            timer1->stop();
        }
    });
}
复制代码

两个定时器:分别是两秒触发一次: 三秒触发一次:;

 

 

 

绘图(绘图类&绘图设备):QPainter->( QPaintEngine )(开发QT的人会用)->QPaintDevice

  画家要在绘图设备上绘画, 绘图设备为窗口, 

  要在.cpp中加头文件#include <QPainter>   

  如果我们要在当前窗口QWidget中画图的话, 需要在类函数(void paintEvent)中处理,重写虚函数(在头文件定义, 在.cpp重写)

      在.h中重新定义虚函数 protected: void paintEvent(QPaintEvent *);

      在.cpp中重写虚函数     void Widget::paintEvent(QPaintEvent *) {   }

  画家默认画笔是黑色, 要想改变画笔颜色, 需要创建新画笔; 

复制代码
void Widget::paintEvent(QPaintEvent *)
{
    //创建画家类
    QPainter p(this);   //指定绘图设备
    //画背景图
    p.drawPixmap(0, 0, QPixmap(":/Image/bk.jpg"));     //画图片

    //创建画笔 ------ 轮廓
    QPen pen;
    pen.setColor(/*Qt::green*/QColor(0, 255, 0));      //可用系统自带的颜色宏, 也可设置RGB自己配置颜色
    pen.setWidth(10);               //宽度为 10像素
    pen.setStyle(Qt::DotLine);      //设置画笔风格为点加线

    //创建画刷 ------ 闭合区域内上色
    //QBrush brush(Qt::blue);         //画刷颜色
    QBrush brush(QPixmap(":/Image/face.png"));

    p.setBrush(brush);              //将新画刷给画家类
    p.setPen(pen);                  //将新画笔给画家类

    //画直线
    p.drawLine(QPoint(100, 100), QPoint(300, 500));
    //画圆
    p.drawEllipse(QPoint(200, 200), 100, 50);
    //画矩形
    p.drawRect(400, 200, 200, 200);     //QRectf 浮点型宽高   QRect整型宽高

    //写字
    //const QString &family(字体名字), int pointSize = -1(字体大小), int weight = -1(加粗), bool italic = false(倾斜)
    QFont font("华文彩云", 48, 75, true);
    p.setFont(font);
    p.drawText(100, 400, "我在写字");     //字体颜色取决于画笔颜色 要改变字体大小和样式,需要使用QFont
    
    //int widget = this->widget();      //获取当前窗口宽度
    //int height = this->height();      //获取当前窗口高度
}
复制代码

 

 

手动刷新窗口: 

  在ui界面内放入一个按钮, 然后通过画笔在ui界面内创建一张图片, 每点击一次按钮, 使图片发生一次移动; 

  首先在ui界面内放入一个按钮(btn_move);

  在.h中重新定义虚函数和定义私有类成员变量x, 然后在.cpp中重写虚函数

      在.h中重新定义虚函数 protected: void paintEvent(QPaintEvent *);

      在.cpp中重写虚函数     void Widget::paintEvent(QPaintEvent *) {   }

widget.h

复制代码
#ifndef WIDGET_H
#define WIDGET_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();
protected: /* * 1. 回调函数(所有虚函数都是回调函数) * 2. 此函数不需要用户调用, 再刷新窗口时自动调用 * 1.窗口显示; * 2.最大化, 最小化; * 3.窗口被遮挡,重新显示的时候 * 4.用户强制刷新的时候 * 5....... * 3. 如果想使用画家类在窗口中画图, 操作必须在paintEvent函数中完成 */ void paintEvent(QPaintEvent *); private: Ui::Widget *ui; int x; }; #endif // WIDGET_H
复制代码

widget.cpp

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    x = 20;     //给图片的横坐标赋初值
    ui->setupUi(this);
    
    connect(ui->btn_move, &QPushButton::clicked, this, [=]()
    {
        //刷新窗口
        update();   //更新窗口, 系统会自动调用paintEvent函数    使用lambda表达式,需要在.pro文件加 CONFIG += c++11
    });
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    //创建画家类
    QPainter p(this);   //指定绘图设备
    x += 5;             //每点击一次,横轴坐标加5
    if(x > this->width())   //当图片移出窗口时, 再重置x轴位置为20
    {
        x = 20;
    }
    p.drawPixmap(x, 100, QPixmap(":/Image/face.png"));  //画笑脸
}
复制代码

 点击移动按钮后, 

 

绘图设备:

  上面说到,如果在QWidget中画图,需要在类函数(void paintEvent)中操作,但是在绘图设备(QPixmap QImage QPicture QBitmap)中,可以直接在其中处理;

  .h

复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

protected:
    void paintEvent(QPaintEvent *);

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
复制代码

  .cpp

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <QPicture>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 在QPixmap中画图
    QPixmap pix(300 ,300);      //创建绘图设备 并初始化绘图设备(纸)的大小
    pix.fill(Qt::blue);
    QPainter p(&pix);
    p.setPen(QPen(Qt::gray, 10));
    p.drawRect(10, 10, 280, 280);
    p.drawEllipse(150, 150, 50, 50);
    pix.save("D:\\mypixmap.png");

    // 在QImage中画图
    QImage Image(300 ,300, QImage::Format_RGB32);      //创建绘图设备 并初始化绘图设备(纸)的大小
    Image.fill(Qt::red);
    //QPainter p(&Image);
    p.begin(&Image);    //重新指定绘图设备
    /*指定绘图设备2种方式:1、构造函数中(参数是绘图设备);     2、begin(参数是绘图设备);  end();*/
    p.setPen(QPen(Qt::gray, 10));
    p.drawRect(10, 10, 280, 280);
    p.drawEllipse(150, 150, 50, 50);
    p.end();
    Image.save("D:\\myimage.png");

    // 在QPicture中画图
    // 1、保存的是绘画步骤 -- 画家类
    // 2、不是图片,save()出来的是是二进制文件
    QPicture pic;      //创建绘图设备
    //QPainter p(&Image);
    p.begin(&pic);    //重新指定绘图设备
    /*指定绘图设备2种方式:1、构造函数中(参数是绘图设备);     2、begin(参数是绘图设备);  end();*/
    p.setPen(QPen(Qt::gray, 10));
    p.drawRect(10, 10, 280, 280);
    p.drawEllipse(150, 150, 50, 50);
    p.end();
    pic.save("D:\\mypic.dzyssb");          //保存的是二进制文件(后缀名可随意更改),若想使用该二进制文件,就得加类函数(void paintEvent)

}
//QPixmap  QImage  QPicture  QBitmap(黑白照片)
//QBitmap  父类为 QPixmap,加载的是彩色,但是打印出来的是黑白的,只能显示2种颜色
//QPixmap  --  图片类,主要用于显示,它针对显示器显示 做了特殊优化,依赖于平台(不同平台(win、linux),加载到内存中的格式不同),只能在主线程(ui线程)中使用
//QImage -- 图片类,不依赖平台(加载到内存中的格式相同)(图片传输,可以在多线程中对其进行操作)
Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    QPainter p(this);
    QPicture pic;
    pic.load("D:\\mypic.dzyssb");       //解析保存的QPicture文件
    p.drawPicture(100, 100, pic);
}
复制代码

 

不规则窗口(无边框图片):

widget.h

复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

protected:
    void paintEvent(QPaintEvent *);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);

private:
    Ui::Widget *ui;
    QPixmap pix;
    QPoint pt;  //鼠标左键点击的位置与图片左上角的距离

};

#endif // WIDGET_H
复制代码

widget.cpp

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <QMouseEvent>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    //加载图片
    pix.load("D:\\sunny.png");
    //去掉边框
    this->setWindowFlags(Qt::FramelessWindowHint);
    //设置背景透明
    this->setAttribute(Qt::WA_TranslucentBackground);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *e)
{
    //在窗口中将图片画出来
    QPainter p(this);
    p.drawPixmap(0, 0, pix);
}

void Widget::mousePressEvent(QMouseEvent *e)
{
    //重写鼠标事件,以用于处理打印出来的无边框图片
    if(e->button() == Qt::LeftButton)
    {
        //求差值 鼠标左键点击的点 减 图片左上角的点坐标
        pt = e->globalPos() - this->frameGeometry().topLeft();     //frameGeometry()返回值是QRect  返回的是矩形区域(窗口区域)  .topLeft() 是窗口左上角的位置
    }
    else if(e->button() == Qt::RightButton)
    {
        //关闭窗口
        this->close();
    }
}
void Widget::mouseMoveEvent(QMouseEvent *e) { //move x, y使用的是屏幕坐标系 //e->x(), e->y() 窗口坐标系 Widget //this->move(e->x(), e->y()); //不能用e->x(), e->y(), 坐标系不统一 //this->move(e->globalX(), e->globalY()); //不能用e->globalX(), e->globalY(),坐标系虽然统一,但是鼠标点下瞬间,会将这个点击位置置为图片左上角坐标原点 this->move(e->globalPos() - pt); }
复制代码

 

posted @   大白不会敲代码  阅读(447)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示