小编个人主页详情<---请点击
小编个人gitee代码仓库<---请点击
Qt系列专栏<---请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
目录
前言
【Qt】Qt窗口(八)QFontDialog字体对话框,QInputDialog输入对话框的使用,小结------书接上文 详情请点击<------,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍------【Qt】Qt系统相关(一)内容简介,事件概念,事件的处理
一、内容简介
- 我们知道Qt虽然是跨平台的C++开发框架,但是Qt的很多能力其实都是操作系统提供的,Qt做的只不过是封装系统的API,我们的GUI程序是运行在操作系统上的,需要系统给我们提供支持,而对于开发者来讲,直接去调用原生的系统调用,以及学习不同平台下的系统调用接口成本相对较高
- 所以Qt封装系统调用给开发者使用,Qt封装后的系统调用相对简单,并且支持跨平台,在Windows下替换调用Windows的系统调用,在Linux下替换调用Linux的系统调用,所以也就产生了Qt封装的系统调用
- Qt封装的系统相关的调用分为如下5个部分,在本文以及后面的文章中小编会进行详细的讲解
(1)事件
(2)文件操作
(3)多线程编程
(4)网络编程
(5)多媒体(音频,视频)
二、事件概念
- 谈起事件,我们不得不提起我们之前学习的信号槽,我们所了解的信号槽也就是,用户进行的各种操作,就可能会产生出信号,此时我们就可以给某种信号指定槽函数,当信号触发的时候,就可以自动的执行到对应的槽函数
- 事件和信号槽非常类似,用户进行的各种操作,也会产生事件,程序员同样可以给事件关联上处理函数(处理的逻辑),当事件触发的时候,就可以执行到对应的代码
- 而事件本身是操作系统提供的机制,Qt事件的本质其实就是对操作系统事件机制的封装,其实即使Qt事件对操作系统的事件机制进行了一定程度的封装,但是Qt事件代码的编写仍然不是很方便,所以Qt又对Qt事件进行了进一步的封装,此时就得到了信号槽,所以Qt事件和信号槽的关系如下
(1)信号槽就是对Qt事件的进一步封装
(2)Qt事件是信号槽的底层机制 - 那么既然信号槽是Qt事件的进一步封装,那为什么我们只学习信号槽不就好了,为什么还需要Qt事件呢?其实我们使用Qt进行开发程序的过程中,绝大部分和用户之间进行的交互都是通过信号槽来完成的,但是在一些特殊情况下,比如用户进行的动作行为,Qt没有提供对应的信号,所以此时信号槽就无法起作用了
- 所以此时Qt事件就可以上场了,通过重写事件处理函数的方式,来手动处理事件的响应逻辑,所以Qt事件的使用,可以让我们程序员根据实际的需要进行更深度的定制化diy(自定义)操作了
- 用户进行很多的操作,此时就会产生很多的信号,别忘了信号的底层是事件,并且有些事件,Qt并没有进行封装对应的信号,所以Qt事件是比信号要多的,所以用户进行很多的操作,不仅会产生很多的信号,还会产生更多的事件

- Qt中同样对事件这个概念使用类QEvent类进行描述,基于QEvent作为基类,派生出很多的派生类代表各种具体的事件,例如QMouseEvent表示鼠标事件,QKeyEvent表示键盘事件,QTimerEvent表示定时器事件,QDropEvent表示拖拽事件,QInputEvent表示输入事件,QPaintEvent表示窗口绘制事件,当然上图这些派生类并不全,小编仅仅是将比较重要的事件列举了出来
- 那么我们要认识到,在不同的场景中,会触发不同的事件,与之对应的要关注的点就不一样,所以在这些事假派生类中就会包含一些对应的不同的属性,例如QMouseEvent鼠标事件中就包含鼠标按下的位置,鼠标哪个键被按下等属性,再例如QKeyEvent键盘事件中就包含是键盘上的哪个键被按下,再比如QPaintEvent窗口绘制事件中就包含一些和屏幕绘制,窗口绘制相关的属性
- 所以我们该如何使用Qt事件呢?小编可以简要讲解一下吗,其实也就是让一段代码和某个事件关联起来,当事件触发的时候,就能执行到这段代码,那么我们这样一看,好像这个事件也跟信号槽差不了多少,实则不然,信号槽是通过一个connect来完成信号和槽的关联,而对于事件来讲却不是这样的
- 事件的实现用到的是其实是C++的继承与多态的机制,首先创建一个派生类继承一个基类控件,在派生类中重写基类已有的某个事件处理函数,这样当事件触发的时候,此时就会通过多态指向谁调用谁的机制,去执行到我们在派生类中重写的事件处理函数了,关于C++的继承与多态的讲解,如下
(1)【c++】面向对象三大特性之------继承(菱形继承详细讲解)
(2)【c++】面向对象三大特性之------多态
三、事件的处理
鼠标进入和离开控件的时候,打印日志

- 接下来我们就正式开始学习事件的处理,所以我们要学习什么事件的处理呢?我们学习鼠标事件的处理,处理一下鼠标的进入和鼠标的离开,上图是一个Widget窗口,其中有一个Label标签,鼠标移动到Label标签上对应上图红色箭头,代表鼠标进入这个控件,鼠标从Label标签上移动走对应上图蓝色箭头,也就代表鼠标离开这个控件,与之对应的事件处理函数,鼠标进入对应enterEvent,鼠标离开leaveEvent

- 接下来我么打开Qt官方文档来学习一下鼠标进入enterEvent和鼠标离开leaveEvent事件处理函数,所以此时我们来看enterEvent,左上角的virtual表示这个事件处理函数是一个虚函数,所以enterEvent这个事件处理函数可以在派生类中被重写,当鼠标进入一个控件的时候,就会触发一个事件给这个控件,此时就可以通过事件处理函数函数enterEvent的形参来接收这个事件,接下来就会去执行enterEvent中的代码逻辑

- 同样的道理对于事件处理函数leaveEvent也是如此,这里小编就不过多赘述了,所以接下来我们创建一个项目名为enterEventAndLeaveEvent,基类为QWidget,派生类为Widget的项目,接下来我们点击ui文件,进入Qt Designer

- 所以此时我们拖拽左侧红框内的控件,然后调整成上图界面即可,接下来我们选中标签控件,将右侧的边框属性frameShape选中为box,此时标签控件的边框比较明显,这样可以清晰的观察鼠标进入或者离开标签控件,objectName保持不变


- 接下来我们就需要创建QLabel的派生类,然后重写鼠标进入enterEvent还有鼠标离开leaveEvent这两个事件处理函数,此时我们创建派生类Label继承自基类QLabel,左上角点击文件,选择新建文件或项目,接下来在左侧的文件和类中选择C++,然后继续选择C++ Class,接下来派生类输入Label,基类输入QLabel,然后勾选上Include QWidget,还有信号槽Add Q_OBJECT,虽然我们不使用,但是包含一下也没有坏处

- 此时就可以让Qt帮我们生成继承自QLabel的派生类Label,如上关于这个类的声明相关的label.h头文件就有了,关于这个类的label.cpp源文件就有了
cpp
#ifndef LABEL_H
#define LABEL_H
#include <QWidget>
#include <QLabel>
class Label : public QLabel
{
Q_OBJECT
public:
Label(QWidget* parent);
public slots:
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
};
#endif // LABEL_H
- 那么在.h头文件中,我们加上基类QLabel的头文件#include <QLabel>,然后在Label的构造函数的形参中加上QWidget* parent,主要是为了使用对象树机制,将父元素传参给基类QLabel,然后使用基类的对象树机制将派生类一并挂接到对象树中指定的的父元素下
- 由于我们想要在派生类Label中重写基类QLabel的虚函数,所以我们声明鼠标进入enterEvent还有鼠标离开leaveEvent这两个事件处理函数,但是要注意,这里我们在进行声明的时候一定要注意派生类Label中的函数名和参数的顺序,个数,类型要和基类QLabel中的函数一致
cpp
#include "label.h"
#include <QDebug>
Label::Label(QWidget* parent)
: QLabel(parent)
{
}
void Label::enterEvent(QEvent *event)
{
(void)event;
qDebug() << "enterEvent" << '\n';
}
void Label::leaveEvent(QEvent *event)
{
(void)event;
qDebug() << "leaveEvent" << '\n';
}
- 所以此时我们在派生类Label的.cpp文件中对于构造函数的参数设置为QWidget* parent,所以此时在初始化列表中传入父元素parent调用基类QLabel的构造函数,将派生类Label设置进对象树中父元素parent下即可
- 接下来就要实现enterEvent还有鼠标离开leaveEvent这两个事件处理函数了,这里我们就先使用(void)event进行类型转换消除警告,接下来使用qDebug()进行日志的打印即可,很简单
运行结果如下
- 不对呀,小编,虽然我们重写了鼠标进入enterEvent还有鼠标离开leaveEvent这两个事件处理函数,但是此时小编使用鼠标进入标签,鼠标离开标签都没有任何日志信息的打印,这是为什么呢?

- 其实答案在ui文件中,此时我们点击ui文件,在右上角对象和类,也就是说我们在界面上创建的label其实是基类QLabel,不是我们实现的派生类Label,所以此时鼠标进入或者离开label,根据C++多态的指向谁调用谁,此时父类的指针指向的是基类QLabel,所以执行的是基类QLabel的鼠标进入enterEvent还有鼠标离开leaveEvent这两个事件处理函数,自然也就不会我们在派生类Label中重写的enterEvent还有鼠标离开leaveEvent这两个事件处理函数了
- 所以也就意味着,此时我们要确保界面上的这个label是我们自己定义的派生类Label的实例,根据C++多态的指向谁调用谁,此时父类的指针才会指向派生类Label,才会执行我们在派生类Label中重写的鼠标进入enterEvent还有鼠标离开leaveEvent这两个事件处理函数,而我们在Qt Designer无论如何拖拽左侧的控件,对于标签始终都是QLabel,那么我们如何做才能将界面上的QLabel修改成Label呢?

- 所以此时我们右击界面上的QLabel标签,然后选择提升为


- 所以上图左侧此时我们添加提升的类的名称为Label,头文件为label.h,然后点击添加,接下来上图中央,我们选中Label,然后点击提升,特别注意的是,这里我们添加提升的类的名称和头文件的时候,一定要确保我们添加的提升的类的名称和头文件和我们自定义的类的名称和头文件要匹配

- 此时当我们点击了提升之后,如上图,label对象原本为基类QLabel,此时就被类型提升为了派生类Label,所以此时通过提升为这样的方式,我们可以把Qt Designer中拖拽上去的控件类型转换为自定义的控件类型
运行结果如下
- 所以此时鼠标进入Label控件中,就产生事件,根据C++多态中的指向谁调用谁的机制,由于父类的指针指向的是派生类Label,所以就会执行派生类的enterEvent事件处理函数,进而打印鼠标进入控件的日志
- 同样的,鼠标离开Label后,同样也会产生事件,根据C++多态中的指向谁调用谁的机制,由于父类的指针指向的是派生类Label,所以就会执行派生类的leaveEvent事件处理函数,就打印鼠标离开控件的日志
- 即当前鼠标进入和鼠标离开这两个事件就顺利的被我们捕获,并且执行了我们自定义的enterEvent和leaveEvent这两个事件处理函数
随机按钮,版本三的实现
- 此时我们有了事件这个法宝,很久之前,小编在讲解geometry的时候,实现了一个向兄弟借款的程序 关于向兄弟借款的程序,在第三点随机按钮的实现中进行的讲解,详情请点击<------,在文章中,小编说,使用到Qt中的事件机制,等到后面合适的时机,小编会进行讲解Qt的事件机制,此时事件机制我们学了,所以下面小编带领大家实现一下版本三,即只要鼠标挪到按钮上,就会让按钮随机移动
- 基本逻辑很简单,也就是使用派生类PushButton继承自基类QPushButton,然后重写鼠标进入对应的事件处理函数enterEvent即可,这里的重写也就是将生成随机位置的逻辑放进去即可


- 所以此时我们就需要使用派生类PushButton继承自基类QPushButton了,所以此时我们鼠标点击左上角的文件,选择新建文件或项目,接下来在左侧的文件和类中选择C++,然后继续选择C++ Class,将生成的类的名称命名为PushButton,然后填充继承的类为QPushButton即可,接下来勾选上Include QWidget添加头文件,接下来勾选Add Q_OBJECT主要是为了使用信号槽机制,接下来一路下一步即可

- 此时就可以让Qt帮我们生成继承自QPushButton的派生类PushButton,如上关于这个类的声明相关的.h头文件就有了,关于这个类的.cpp源文件就有了
cpp
#ifndef PUSHBUTTON_H
#define PUSHBUTTON_H
#include <QWidget>
#include <QPushButton>
class PushButton : public QPushButton
{
Q_OBJECT
public:
PushButton(QWidget* parent);
void enterEvent(QEvent* event);
};
#endif // PUSHBUTTON_H
- 那么在.h头文件中,我们加上基类QPushButton的头文件#include <QPushButton>,然后在QPushButton的构造函数的形参中加上QWidget* parent,主要是为了使用对象树机制,将父元素传参给基类QDialog,然后使用基类的对象树机制将派生类一并挂接到对象树中指定的的父元素下,接下来我们要重写的是鼠标进入enterEvent,所以这里我们在.h文件中声明一下enterEvent事件处理函数即可
cpp
#include "pushbutton.h"
#include <ctime>
PushButton::PushButton(QWidget* parent)
: QPushButton(parent)
{
}
void PushButton::enterEvent(QEvent* event)
{
(void)event;
int width = this->geometry().width();
int height = this->geometry().height();
int x = rand() % width;
int y = rand() % height;
// ui->pushButton_reject->move(x, y);
this->move(x, y);
}
- 所以此时我们在派生类PushButton的.cpp文件中对于构造函数的参数设置为QWidget* parent,所以此时在初始化列表中传入父元素parent调用基类QDialog的构造函数,将派生类Dialog设置进对象树中父元素parent下即可
- 接下来我们需要重写鼠标进入对应的事件处理函数enterEvent,这里的重写也就是将生成随机位置的逻辑放进去即可,需要将调用move的修改为this指针,即我们需要移动的是当前this指针对应的派生类PushButton


- 所以此时我们点击ui,进入Qt Designer,右击残忍拒绝按钮,选择提升为,接下来将派生类PushButton以及对应的头文件pushbutton.h进行添加,然后点击提升

- 此时当我们点击了提升之后,如上图,pushButton_reject对象的类型原本为基类QPushButton,此时就被类型提升为了派生类PushButton
运行结果如下
- 嘶,感觉和我预想的不太一样,小编,当鼠标最初进入残忍拒绝按钮的时候,一瞬间残忍拒绝按钮就随机出现在了Widget窗口的左上角附近
- 接下来无论小编如何让鼠标进入残忍拒绝按钮,那么此时残忍拒绝按钮始终出现在Widget窗口的左上角附近
- 小编这样看来,我仿佛嗅到了一丝bug的味道,是的,那么什么bug呢?逻辑上又是哪里出现了问题呢?
cpp
#include "pushbutton.h"
#include <ctime>
PushButton::PushButton(QWidget* parent)
: QPushButton(parent)
{
}
void PushButton::enterEvent(QEvent* event)
{
(void)event;
// int width = this->geometry().width();
// int height = this->geometry().height();
int width = 800;
int height = 600;
int x = rand() % width;
int y = rand() % height;
// ui->pushButton_reject->move(x, y);
this->move(x, y);
}
- 其实问题就出现在通过this指针获取宽度和高度上,首先我们要清楚,在之前的代码中,我们实现的是信号和槽,那么这个槽函数的实现是在Widget窗口的.cpp文件中实现,即槽函数中调用的this指针都是指向的Widget窗口的,所以可以正常获取Widget窗口的宽度和高度,然后进行取模,确保不超出Widget窗口的范围
- 而我们是直接将代码复制粘贴到PushButton的.cpp文件中,也就意味着此时的this指针指向的就不是Widget窗口了,此时的this指针指向的是派生类PushButton,也就意味着我们获取的是残忍拒绝按钮的宽度和高度,然后进行取模,确保不超出残忍拒绝按钮的窗口范围
- 而残忍拒绝按钮的宽度不宽,高度不高,所以才会出现当鼠标最初进入残忍拒绝按钮的时候,一瞬间残忍拒绝按钮就随机出现在了Widget窗口的左上角附近,并且接下来无论小编如何让鼠标进入残忍拒绝按钮,那么此时残忍拒绝按钮始终出现在Widget窗口的左上角附近
- 如果我们可以在PushButton中获取Widget窗口的宽度和高度就好了,可以吗?可以呀,之前我们是通过this指针的方式获取Widget窗口的宽度和高度,这里不可以使用this指针,本质上是由于this指针不指向Widget窗口了,而是指向PushButton,所以我们期望有一个Widget窗口的指针,所以我们该如何做呢?
cpp
#ifndef PUSHBUTTON_H
#define PUSHBUTTON_H
#include <QWidget>
#include <QPushButton>
class PushButton : public QPushButton
{
Q_OBJECT
public:
PushButton(QWidget* parent);
void enterEvent(QEvent* event);
private:
QWidget* widget;
};
#endif // PUSHBUTTON_H
- 我有一计,别忘了这个PushButton是要被挂接到对象树上的,而要挂接到对象树上就需要指定父元素,父元素是谁?不就正好是Widget窗口呀,所以我们在PushButton的.h头文件中关于PushButton类的声明添加一个QWidget的指针对象widget即可,注意这里不能采用Widget类型的指针对象,必须是QWidget类型的,因为我们挂接到对象树上使用的是就是QWidget类型的指针
cpp
#include "pushbutton.h"
#include <ctime>
PushButton::PushButton(QWidget* parent)
: QPushButton(parent)
, widget(parent)
{
}
void PushButton::enterEvent(QEvent* event)
{
(void)event;
// int width = this->geometry().width();
// int height = this->geometry().height();
int width = widget->geometry().width();
int height = widget->geometry().height();
int x = rand() % width;
int y = rand() % height;
// ui->pushButton_reject->move(x, y);
this->move(x, y);
}
- 那么我们在PushButton的构造函数中,使用QWidget类型的指针widget保存指向Widget窗口的指针parent即可,所以此时我们就有了指向Widget窗口的指针widget,那么我们在enterEvent事件处理函数中使用指向Widget窗口的指针widget获取Widget窗口的宽度和高度即可
运行结果如下
- 所以此时我们一使用按钮进入残忍拒绝按钮,那么残忍拒绝按钮就会触发鼠标进入事件,进而就会去执行我们在派生类PushButton中重写的事件处理函数enterEvent让残忍拒绝按钮随机出现
- 所以此时我的好兄弟一将鼠标进入残忍拒绝按钮,那么此时残忍拒绝按钮就会随机出现,那么我的好兄弟就不断尝试尝试,运气好,点击到了残忍拒绝按钮,残忍按钮被点击到了我们就不让好兄弟借钱了吗?不可能
- 不可能,别忘了我们对于残忍拒绝按钮的点击信号相关的槽函数没有注释掉,所以也就意味着此时就会触发残忍拒绝按钮的clicked点击信号,那么就会去执行关联的槽函数,在槽函数的逻辑中同样会让残忍拒绝按钮就会随机出现
- 所以我的好兄弟无奈,只能点击欣然同意,所以此时我们就成功的借到了5块钱,很完美
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!




