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); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律