1. 事件
众所周知Qt是一个基于C++的框架,主要用来开发带窗口的应用程序(不带窗口的也行,但不是主流)。我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在Qt框架内部为我们提供了一系列的事件处理机制,当窗口事件产生之后,事件会经过:事件派发 -> 事件过滤->事件分发->事件处理几个阶段。Qt窗口中对于产生的一系列事件都有默认的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作。
事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
每一个Qt应用程序都对应一个唯一的 QApplication应用程序对象,然后调用这个对象的exec()函数,这样Qt框架内部的事件检测就开始了(程序将进入事件循环来监听应用程序的事件)。
cppint main(int argc, char *argv[]) // 程序入口函数 // argc:命令行参数个数 // argv:命令行参数数组 { QApplication a(argc, argv); // ① 创建 QApplication 应用程序对象 // QApplication 管理整个 GUI 程序的生命周期 // 负责: // - 初始化 Qt 系统 // - 处理命令行参数 // - 管理事件循环 // 注意:每个 GUI 程序有且只有一个 QApplication 对象 // 必须在任何其他 Qt 对象之前创建 MainWindow* w = new MainWindow; // ② 在堆上创建主窗口对象 // 没有指定父对象(new MainWindow 无 parent 参数) // w 是顶层窗口(独立窗口) // 注意:没有 delete,因为 Qt 对象树会管理 // 但 MainWindow 是顶层窗口,需要手动 delete 或设置关闭时销毁 w.show(); // ③ 显示主窗口 // show() 使窗口可见 // 调用后窗口才出现在屏幕上 return a.exec(); // ④ 进入 Qt 事件循环 // exec() = execute(执行) // 程序在这里"卡住",开始无限循环处理事件 // 直到用户关闭所有窗口或调用 quit() 才返回 // 返回值作为 main 的返回值(0 表示正常退出) }int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow* w = new MainWindow; w.show(); return a.exec(); }事件在Qt中产生之后的分发过程是这样的:
当事件产生之后,Qt使用应用程序对象调用notify()函数将事件发送到指定的窗口:
cpp[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);事件在发送过程中可以通过事件过滤器进行过滤,默认不对任何产生的事件进行过滤。
cpp// 需要先给窗口安装过滤器, 该事件才会触发 [virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:
cpp[override virtual protected] bool QWidget::event(QEvent *event);事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函数),比如:鼠标事件:
cpp// 鼠标按下 [virtual protected] void QWidget::mousePressEvent(QMouseEvent *event); // 鼠标释放 [virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event); // 鼠标移动 [virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);2. 事件处理器函数
通过上面的描述可以得知:Qt的事件处理器函数处于食物链的最末端,每个事件处理器函数都对应一个唯一的事件,这为我们重新定义事件的处理动作提供了便利。另外,Qt提供的这些事件处理器函数都是回调函数,也就是说作为使用者我们只需要指定函数的处理动作,关于函数的调用是不需要操心的,当某个事件被触发,Qt框架会调用对应的事件处理器函数。
QWidget类是Qt中所有窗口类的基类,在这个类里边定义了很多事件处理器函数,它们都是受保护的虚函数。我们可以在Qt的任意一个窗口类中重写这些虚函数来重定义它们的行为。下面介绍一些常用的事件处理器函数:
2.1 鼠标事件
鼠标按下事件
当鼠标左键、鼠标右键、鼠标中键被按下,该函数被自动调用,通过参数可以得到当前按下的是哪个鼠标键
cpp[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);鼠标释放事件
当鼠标左键、鼠标右键、鼠标中键被释放,该函数被自动调用,通过参数可以得到当前释放的是哪个鼠标键
cpp[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);鼠标双击事件
当鼠标双击该函数被调用,通过参数可以得到是通过哪个鼠标键进行了双击操作。
cpp[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event);鼠标进入事件
当鼠标进入窗口的一瞬间,触发该事件,注意:只在进入的瞬间触发一次该事件
cpp[virtual protected] void QWidget::enterEvent(QEvent *event);鼠标离开事件
当鼠标离开窗口的一瞬间,触发该事件,注意:只在离开的瞬间触发一次该事件
cpp[virtual protected] void QWidget::leaveEvent(QEvent *event);2.2 键盘事件
键盘按下事件
当键盘上的按键被按下了,该函数被自动调用,通过参数可以得知按下的是哪个键。
cpp[virtual protected] void QWidget::keyPressEvent(QKeyEvent *event);键盘释放事件
当键盘上的按键被释放了,该函数被自动调用,通过参数可以得知释放的是哪个键。
cpp[virtual protected] void QWidget::keyReleaseEvent(QKeyEvent *event);2.3 窗口重绘事件
当窗口需要刷新的时候,该函数就会自动被调用。窗口需要刷新的情景很多,比如:窗口大小发生变化,窗口显示等,另外我们还可以通过该函数给窗口绘制背景图,总之这是一个需要经常被重写的一个事件处理器函数。
cpp[virtual protected] void QWidget::paintEvent(QPaintEvent *event);2.4 窗口关闭事件
当窗口标题栏的关闭按钮被按下并且在窗口关闭之前该函数被调用,可以通过该函数控制窗口是否被关闭。
cpp[virtual protected] void QWidget::closeEvent(QCloseEvent *event);2.5 重置窗口大小事件
当窗口的大小发生变化,该函数被调用。
cpp[virtual protected] void QWidget::resizeEvent(QResizeEvent *event);除此之外,关于Qt窗口提供的其他事件处理器函数还有很多,感兴趣的话可以仔细阅读Qt的帮助文档,窗口的事件处理器函数非常好找,规律是这样的:
受保护的虚函数
函数名分为两部分: 事件描述+Event
函数带一个事件类型的参数
3. 重写事件处理器函数
由于事件处理器函数都是虚函数,因此我们就可以添加一个标准窗口类的派生类,这样不仅使子类继承了父类的属性,还可以在这个子类中重写父类的虚函数,总起来说整个操作过程还是 so easy 的。
下面举个例子:
创建一个Qt项目,添加一个窗口类(让其从某个标准窗口类派生)
在子类中重写从父类继承的虚函数(也就是事件处理器函数)
3.1 头文件
cpp#include <QMainWindow> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); protected: // 重写事件处理器函数 void closeEvent(QCloseEvent* ev); void resizeEvent(QResizeEvent* ev); private: Ui::MainWindow *ui; };3.2 源文件
cpp#include "mainwindow.h" #include "ui_mainwindow.h" #include <QCloseEvent> #include <QMessageBox> #include <QResizeEvent> #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::closeEvent(QCloseEvent *ev) { QMessageBox::Button btn = QMessageBox::question(this, "关闭窗口", "您确定要关闭窗口吗?"); if(btn == QMessageBox::Yes) { // 接收并处理这个事件 ev->accept(); } else { // 忽略这个事件 ev->ignore(); } } void MainWindow::resizeEvent(QResizeEvent *ev) { qDebug() << "oldSize: " << ev->oldSize() << "currentSize: " << ev->size(); }QCloseEvent类是QEvent类的子类,程序中使用的accept()或者ignore()的作用请参考QEvent类(3.2Qt事件之事件分发器的1. QEvent)。
3.3 效果
在上面重写的closeEvent事件中添加了关闭窗口的判断,这样就可以避免误操作导致窗口被关闭了,效果如下:
如果想要时时检测窗口大小,就可以重写窗口的resizeEvent事件,这样就可以得到窗口的最新尺寸信息了:
4. 自定义按钮
基于Qt提供的事件处理器函数,我们可以非常轻松地按照自己的想法制作出一个按钮,按钮的要求如下:
从视觉上看是一个不规则按钮(按钮实际上都是矩形的)
1.按钮上需要显示指定的背景图片
2.按钮在鼠标的不同操作阶段(无操作、鼠标悬停、鼠标按下)能够显示不同的背景图
3.按钮效果如下:
4.1 添加子类
新添加的按钮类可以让它继承 QPushButton,也可以让它继承其他的窗口类(代价是当鼠标点击事件触发之后需要自己发射自定义信号),这里让添加的子类从QWidget类派生。
自定义类头文件
cpp#ifndef MYBUTTON_H #define MYBUTTON_H #include <QWidget> class MyButton : public QWidget { Q_OBJECT public: explicit MyButton(QWidget *parent = nullptr); void setImage(QString normal, QString hover, QString pressed); protected: void mousePressEvent(QMouseEvent* ev); void mouseReleaseEvent(QMouseEvent* ev); void enterEvent(QEvent* ev); void leaveEvent(QEvent* ev); void paintEvent(QPaintEvent* ev); signals: void clicked(); private: QPixmap m_normal; QPixmap m_press; QPixmap m_hover; QPixmap m_current; }; #endif // MYBUTTON_H自定义类源文件
cpp#include "mybutton.h" #include <QPainter> MyButton::MyButton(QWidget *parent) : QWidget(parent) { } void MyButton::setImage(QString normal, QString hover, QString pressed) { // 加载图片 m_normal.load(normal); m_hover.load(hover); m_press.load(pressed); m_current = m_normal; // 设置按钮和图片大小一致 setFixedSize(m_normal.size()); } void MyButton::mousePressEvent(QMouseEvent *ev) { // 鼠标被按下, 发射这个自定义信号 emit clicked(); m_current = m_press; update(); } void MyButton::mouseReleaseEvent(QMouseEvent *ev) { m_current = m_normal; update(); } void MyButton::enterEvent(QEvent *ev) { m_current = m_hover; update(); } void MyButton::leaveEvent(QEvent *ev) { m_current = m_normal; update(); } void MyButton::paintEvent(QPaintEvent *ev) { QPainter p(this); p.drawPixmap(rect(), m_current); }4.2 使用自定控件
由于Qt的UI工具箱中提供的都是标准控件,自定义的控件是不能直接拖拽到UI窗口中的,这时我们需要先看一下自定义控件的基类类型:上面自定义的 MyButton 的基类是 QWidget 类型,因此需要往窗口中拖拽一个QWidget类型的标准控件,然后在这个标准控件上鼠标右键:
这样添加的控件类型就变成了自定义的子类类型:
4.3 设置图片
在主窗口中通过添加的按钮的对象,调用子类的成员函数给其添加图片:
mainwindow.cpp
cppMainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // 给自定义按钮设置图片 ui->button->setImage(":/ghost-1.png", ":/ghost-2.png", ":/ghost-3.png"); // 处理自定义按钮的鼠标点击事件 connect(ui->button, &MyButton::clicked, this, [=]() { QMessageBox::information(this, "按钮", "莫要调戏我..."); }); } MainWindow::~MainWindow() { delete ui; }
笔记:
1.Qt窗口中事件的处理过程
在qt中产生一个事件之后,这个事件的路由主要是分为四个过程,第一个过程叫做时间派发,事件派发由qt的应用程序对象来发送的,这个对象会把事件发送到对应的窗口,在发送过程中可以选择对窗口产生的事件进行过滤,如果不过滤事件就会被发送到对应的那个窗口,每个窗口都有一个事件分发器,事件分发器会对事件进行分类,再把分好类的对应的事件发送给对应的事件处理器函数,事件处理器函数负责处理产生的事件,每个事件处理器函数的功能都是非常单一的并且它们都有默认的处理动作。
如果我们想要进行事件的派发就必须在当前的应用程序中创建出一个应用程序对象,这个应用程序对象的类型是QApplication类型,在每个qt应用程序中有且仅有一个应用程序对象,这个对象被创建出来后需要调用exec函数,exec函数被调用之后应用程序对象会在后台维护一个事件循环,这个事件循环是一直去检测我们当前这个窗口有没有产生相关的事件,如果产生了相关的事件,应用程序对象就会调用notify函数,去对事件进行传递,把事件传递到对应的某一个窗口。
第一个参数receiver是事件的接受者,第二个参数QEvent类型的e对象就是产生的事件
QEvent类是所有事件类的基类
通过notify可以对事件进行发送,在发送过程中可以选择对发送的事件进行过滤
第一个参数watched表示要过滤哪一个对象的事件,第二个参数QEvent类型的event对象就是对应的要被过滤的事件
cppint main(int argc, char *argv[]) // 程序入口函数 // argc:命令行参数个数 // argv:命令行参数数组 { QApplication a(argc, argv); // ① 创建 QApplication 应用程序对象 // QApplication 管理整个 GUI 程序的生命周期 // 负责: // - 初始化 Qt 系统 // - 处理命令行参数 // - 管理事件循环 // 注意:每个 GUI 程序有且只有一个 QApplication 对象 // 必须在任何其他 Qt 对象之前创建 MainWindow* w = new MainWindow; // ② 在堆上创建主窗口对象 // 没有指定父对象(new MainWindow 无 parent 参数) // w 是顶层窗口(独立窗口) // 注意:没有 delete,因为 Qt 对象树会管理 // 但 MainWindow 是顶层窗口,需要手动 delete 或设置关闭时销毁 w.show(); // ③ 显示主窗口 // show() 使窗口可见 // 调用后窗口才出现在屏幕上 return a.exec(); // ④ 进入 Qt 事件循环 // exec() = execute(执行) // 程序在这里"卡住",开始无限循环处理事件 // 直到用户关闭所有窗口或调用 quit() 才返回 // 返回值作为 main 的返回值(0 表示正常退出) }
cpp程序启动 ↓ 创建 QApplication a ← 初始化 Qt 系统 ↓ 创建 MainWindow w ← 分配内存,构造窗口 ↓ w.show() ← 窗口显示在屏幕上 ↓ a.exec() ← 进入事件循环(程序"卡"在这里) ↓ 循环处理事件: ├── 鼠标点击 ├── 键盘输入 ├── 窗口重绘 ├── 定时器触发 └── ... ↓ 用户关闭窗口 / 调用 quit() ↓ exec() 返回 ↓ 程序结束,回收资源总结
main函数的固定流程:创建 QApplication 管理程序 → 创建主窗口 → 显示 → 进入事件循环(exec)。程序在 exec 中一直运行,处理用户操作,直到窗口关闭。
事件(Event)和信号槽(Signal & Slot)的关系
事件是底层输入,信号槽是高层封装。事件先发生,Qt 处理事件后可能发射信号。
对比表
维度 事件(Event) 信号槽(Signal & Slot) 本质 底层消息 对象间通信机制 发出者 操作系统/鼠标/键盘 QObject 对象 接收者 event()函数connect连接的槽函数处理方式 重写 xxxEvent()虚函数连接信号到槽 粒度 细(鼠标移动、按键) 粗(点击、状态变化) 关系 1 个事件 → 0 或多个信号 1 个信号 → 多个槽 完整流程
cpp用户按下鼠标 ↓ 操作系统捕获 ↓ Qt 事件循环接收 → 封装为 QMouseEvent ↓ 发送给目标控件的 event() 函数 ↓ event() 分发到 mousePressEvent() ← 这是"事件" ↓ QPushButton 内部处理 mousePressEvent() ↓ 发射 clicked() 信号 ← 这是"信号" ↓ connect 连接的槽函数被调用 ← 这是"槽"代码对比
事件处理(重写虚函数)
cppclass MyWidget : public QWidget { protected: // 重写鼠标按下事件 void mousePressEvent(QMouseEvent *event) override { qDebug() << "鼠标按下了"; QWidget::mousePressEvent(event); // 调用父类继续处理 } };信号槽(连接信号)
cppconnect(btn, &QPushButton::clicked, this, [=]() { qDebug() << "按钮被点击了"; });联系与区别
特性 事件 信号槽 是否需要 connect 不需要,重写虚函数即可 必须 connect 能否过滤 能用事件过滤器 不能直接过滤 传播方向 向上传播(父对象) 无传播,直接到槽 适用场景 自定义控件、精细控制 对象间通信、业务逻辑 信号来源 操作系统 对象主动发射 类比
cpp事件 = 敲门声(物理动作) 信号 = 主人喊"有客人来了"(通知) 槽 = 你去开门(响应) 敲门 → 主人判断 → 喊话 → 开门 事件 → Qt处理 → 发射信号 → 执行槽什么时候用哪个?
cpp// 用事件:需要精细控制鼠标/键盘行为 class MyLabel : public QLabel { protected: void mouseDoubleClickEvent(QMouseEvent *e) override { // 双击时做特殊处理 } }; // 用信号槽:普通按钮点击、状态变化 connect(btn, &QPushButton::clicked, ...); connect(timer, &QTimer::timeout, ...);总结
事件是底层输入(鼠标、键盘等),由操作系统产生,Qt 处理后可能发射信号。信号槽是对象间高层次的通信方式。比如按钮点击:事件(mousePressEvent)→ 信号(clicked)→ 槽(你的 lambda)。
如果没有对事件做任何过滤处理,那默认就不会过滤任何事件。
这个事件没有被过滤就会被发送到对应的窗口,每一个窗口都有一个叫做event的函数,这个函数叫做事件分发器,接收的参数就是发过来的事件,事件分发器接收事件后会分发给对应的事件处理器进行处理。
有事件过滤器、事件分发器、事件过滤器,这些函数不需要程序员去调用,但是程序员可以重写这些函数的行为,因为这些函数都是虚函数,虚函数的特点:可以写一个子类,在子类里继承父类的虚函数,并且重写父类的虚函数,子类重写了虚函数后虚函数的行为就被重新定义了,就可以在子类里按照我们指定的行为进行对应的事件处理。
2.事件处理器函数介绍
QObject 是 Qt 中几乎所有类的基类,是 Qt 对象模型的核心。
核心地位
cppQObject ↑ ├── QWidget(所有可视化控件) │ ↑ │ ├── QPushButton │ ├── QLabel │ └── QMainWindow │ ├── QTimer(定时器) ├── QThread(线程) └── ...几乎所有 Qt 类QObject 提供的核心功能
功能 说明 信号槽机制 connect连接信号和槽对象树 父子关系管理,自动销毁子对象 元对象系统 metaObject()、qobject_cast动态属性 setProperty()/property()事件处理 event()虚函数内存管理 父子关系自动清理 代码示例
cpp// 所有继承 QObject 的类必须写 Q_OBJECT class MyClass : public QObject { Q_OBJECT // ← 启用元对象系统(信号槽必需) public: explicit MyClass(QObject *parent = nullptr) : QObject(parent) {} // ← 传递父对象 };关键 API
cpp// 父子关系 QObject *parent = obj->parent(); // 获取父对象 obj->setParent(parentWidget); // 设置父对象 QList<QObject*> children = obj->children(); // 获取子对象列表 // 类型判断 obj->metaObject()->className(); // 获取类名 qobject_cast<QWidget*>(obj); // 安全类型转换 // 动态属性 obj->setProperty("age", 25); // 设置属性 int age = obj->property("age").toInt(); // 获取属性对象树机制
cppQObject *parent = new QObject; QObject *child1 = new QObject(parent); // 指定父对象 QObject *child2 = new QObject(parent); delete parent; // 自动递归删除 child1 和 child2 // 不需要手动 delete child1 和 child2为什么 QObject 不能拷贝?
cppQObject obj1; QObject obj2 = obj1; // ❌ 编译错误! // QObject 的拷贝构造函数是私有的 // 因为对象有父子关系,拷贝会导致混乱3.事件处理器函数的使用closeEvent resizeEvent
快捷键 F1 帮助文档
如果在程序中调用了这个事件的ignore方法,相当于在当前窗口里边不去处理这个事件,这个事件会向上传递给它的父窗口,以此类推,直到这个事件在最外层这个窗口就被忽略掉了。
yes表示同意关闭当前窗口
点击关闭窗口,弹出消息对话框询问是否关闭,如果选择no相当于关闭事件被忽略了,如果选择yes则表示关闭事件生效,关闭窗口。
拖动窗口,只要窗口大小发生变化,尺寸信息就会实时更新,控制台不断打印出当前窗口的尺寸信息
4.基于事件处理器函数自定义按钮
通过一个窗口去实现一个按钮的功能,基于qwidget实现一个按钮类型的控件
下为博主的代码
void enterEvent(QEvent *ev);需要改为void enterEvent(QEnterEvent *ev);
alt+enter快速切换到源文件
QPixmap 翻译成中文是:"像素图" 或 "位图"。
在 Qt 框架的上下文中,更通俗、更准确的理解是:"Qt 图像/图片类"。
它与
QImage的区别(非常重要):
1.QPixmap:专为屏幕显示优化 。它在底层通常使用与显示硬件(显卡)更兼容的格式,因此绘制在屏幕上速度最快。但是,它操作单个像素非常慢。在不同的平台下QPixmap做了不同的优化。2.
QImage:专为像素级操作优化 。如果你需要读取/修改图片的每一个像素(比如做滤镜、修图、图像识别),一定要用QImage。它的底层是直接访问内存的。QImage主要是进行跨线程的图片传输或者是多线程绘图,能够实现像素级别的修改。3.互相转换 :两者可以无缝转换:
QImage img = pixmap.toImage();或QPixmap pix = QPixmap::fromImage(img);。this是绘图设备
触发了对应的鼠标事件之后,这个窗口并不会被刷新,需要手动刷新窗口
需要调用update函数
想让paintEvent函数执行,并不是在函数里去调用paintEvent,而是去调用update,因此每个鼠标事件函数里都添加update函数表示手动刷新窗口,paintEvent就会调用,打印出新的图片。
如果想让按钮和图片一样大,可以在构造函数里进行设定
最后我们需要把这个MyButton显示到对应的某一个窗口上
因为MyButton的基类是QWidget类型,因此想要使用自定义类型的按钮,就需要在ui界面拖拽一个widget类型的控件
代码:
头文件
mybutton.h
cpp#ifndef MYBUTTON_H #define MYBUTTON_H #include <QWidget> // QWidget 基类 #include <QPainter> // 绘图类 class MyButton : public QWidget { Q_OBJECT public: explicit MyButton(QWidget *parent = nullptr); protected: // ★ 重写的 5 个事件处理函数 void mousePressEvent(QMouseEvent *ev); // 鼠标按下 void mouseReleaseEvent(QMouseEvent *ev); // 鼠标释放 void enterEvent(QEnterEvent *ev); // 鼠标进入 void leaveEvent(QEvent *ev); // 鼠标离开 void paintEvent(QPaintEvent *ev); // 绘制 signals: // 没有定义信号(留空) private: QPixmap m_pixmap; // 存储按钮当前显示的图片 }; #endif // MYBUTTON_H源文件
mybutton.cpp
cpp#include "mybutton.h" MyButton::MyButton(QWidget *parent) : QWidget{parent} { // 构造函数:加载默认图片 m_pixmap.load(":/_20230610180656.jpg"); // 从资源文件加载图片 setFixedSize(300, 300); // 固定按钮大小为 300×300 像素 } // ============================================================ // ★ 鼠标按下事件 // ============================================================ void MyButton::mousePressEvent(QMouseEvent *ev) { m_pixmap.load(":/20240615114601.jpg"); // 按下时切换为"按下状态"的图片 update(); // ★ 触发重绘 // update() 会调用 paintEvent() // 不调用 update 的话,界面上看不到图片变化 } // ============================================================ // ★ 鼠标释放事件 // ============================================================ void MyButton::mouseReleaseEvent(QMouseEvent *ev) { m_pixmap.load(":/_20230610180656.jpg"); // 释放时恢复默认图片 update(); // 触发重绘 } // ============================================================ // ★ 鼠标进入事件 // ============================================================ void MyButton::enterEvent(QEnterEvent *ev) { m_pixmap.load(":/20240615114611.jpg"); // 鼠标进入时切换为"悬停"图片 update(); } // ============================================================ // ★ 鼠标离开事件 // ============================================================ void MyButton::leaveEvent(QEvent *ev) { m_pixmap.load(":/_20230610180656.jpg"); // 鼠标离开时恢复默认图片 update(); } // ============================================================ // ★ 绘制事件(核心) // ============================================================ void MyButton::paintEvent(QPaintEvent *ev) { QPainter p(this); // 创建画笔,绘制到当前控件上 p.drawPixmap(rect(), m_pixmap); // 在控件矩形区域内绘制当前图片 // rect():控件的完整区域(300×300) // m_pixmap:要绘制的图片 }运行效果
cpp初始状态:[ 默认图片 ] 鼠标移入:[ 悬停图片 ] ← enterEvent ↓ 按下 鼠标按下:[ 按下图片 ] ← mousePressEvent ↓ 释放 鼠标释放:[ 默认图片 ] ← mouseReleaseEvent ↓ 移出 鼠标移出:[ 默认图片 ] ← leaveEvent核心知识点
- 事件处理流程
cpp用户操作 → Qt 检测到事件 → 调用对应的事件处理函数 鼠标移入 → enterEvent() 鼠标按下 → mousePressEvent() 鼠标释放 → mouseReleaseEvent() 鼠标移出 → leaveEvent() 需要重绘 → paintEvent()
update()的作用
cppm_pixmap.load("新图片"); update(); // ★ 必须有这行! // 告诉 Qt:"内容变了,需要重新绘制" // Qt 会调用 paintEvent() 重新绘制 // 没有 update() 就不会刷新显示
paintEvent绘制
cppQPainter p(this); // 画笔 p.drawPixmap(rect(), m_pixmap); // 画图片 // rect() = 控件的矩形区域鼠标状态对比
状态 触发事件 显示图片 默认 构造/释放/离开 默认图片 悬停 enterEvent悬停图片 按下 mousePressEvent按下图片 释放 mouseReleaseEvent恢复默认
5.自定义按钮添加信号
对于标准按钮来说,当我们点击之后它就会发射一个叫做clicked的信号,但是对自定义按钮来说不论怎么点都不会发射信号,因此需要进行设置,当点击自定义按钮时也能发射信号。
声明一个宏Q_UNUSED里面的参数表示虽然没有用到这个参数,但是在编译的时候不要提示相关的警告信息了。
还有一个小知识点:因为在当前的MyButton里重写了从父类继承过来的一系列虚函数,如果子类重写了父类的虚函数,父类的虚函数就不能够被执行了,假如说我们在子类里还需要执行父类虚函数里的默认的处理操作,可以在子类的虚函数里重新再调用父类的虚函数。
当前MyButton的父类是QWidget,这段代码说明在子类执行完自己的处理动作后又执行了父类里默认定义的一系列的处理动作。
要不要加要看具体父类虚函数要看具体情况,默认情况下子类重写了父类的虚函数,父类的默认处理动作是不需要被再次执行,但是如果当前按钮类MyButton继承的是QPushButton或者QToolButton,默认的按钮类当它触发了mousePressEvent事件之后会发射clicked信号,此时需要在mousePressEvent的最下面调用一下父类的虚函数让父类的默认虚函数再执行一遍,信号才能够被发射出去。
6.事件的使用-小案例
在 Qt 中实现"将一个子窗口(或弹窗)在其父窗口中居中显示"
cppwin->move( (width() - win->width()) / 2, (height() - win->height()) / 2 );



























































































