[Qt] 系统相关_1 | 常见事件 | 事件分发器 | 过滤器
目录
1、事件介绍
2、事件的处理
3、键盘按键事件
4、鼠标事件
5、定时器
6、事件分发器
7、事件过滤器
1、事件介绍
事件是 应用程序内部或者外部产生的事情或者动作的统称。
- 在 Qt 中使用一个对象来表示一个事件。所有的 Qt 事件均继承于抽象类 QEvent。
- 事件是由系统或者 Qt 平台本身在不同的时刻发出的。
- 当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。
- 一些事件是在用户操作时发出,如键盘事件、鼠标事件等,另一些事件则是由系统本身自动发出,如定时器事件。
常见的 Qt 事件如下:
不同场景下,要关注的点不一样。这些事件的子类中就会包含一些对应的不同属性。
常见事件描述:
从图片中提取的文字信息如下表所示:
事件名称 | 描述 |
鼠标事件 | 鼠标左键、鼠标右键、鼠标滚轮,鼠标的移动,鼠标按键的按下和松开 |
键盘事件 | 按键类型、按键按下、按键松开 |
定时器事件 | 定时时间到达 |
进入离开事件 | 鼠标的进入和离开 |
滚轮事件 | 鼠标滚轮滚动 |
绘屏事件 | 重绘屏幕的某些部分 |
显示隐藏事件 | 窗口的显示和隐藏 |
移动事件 | 窗口位置的变化 |
窗口事件 | 是否为当前窗口 |
大小改变事件 | 窗口大小改变 |
焦点事件 | 键盘焦点移动 |
拖拽事件 | 用鼠标进行拖拽 |
2、事件的处理
事件处理一般常用的方法为:重写相关的 Event 函数。
在 Qt 中,几乎所有的 Event 函数都是虚函数,所以可以重新实现。
比如:在实现鼠标的进入和离开事件时,直接重新实现 enterEvent() 和 leaveEvent() 即可。enterEvent() 和 leaveEvent() 函数原型如下:
【示例一】
(1)新建 Qt 项目
基类选择 QWidget,同时勾选 UI 界面文件:
(2)设计 UI 文件
(3)在项目中新添加一个类
先选中项目名称 QEvent,点击鼠标右键,选择 add new ... ,弹出如下对话框:
(4)定义类名并选择基类
(5)此时项目中会新添加以下两个文件
(6)在帮助文档中查找对应的内容
(7)点击 “显示” 之后
(8)复制 enterEvent(),粘贴在项目文件 "label.h" 中
要想重写父类的函数就需要确保写的函数名字和函数的参数列表完全一致(形参名无所谓)。
(label.h)
#ifndef LABEL_H
#define LABEL_H
#include <QWidget>
#include<QLabel>
class Label : public QLabel
{
Q_OBJECT
public:
Label(QWidget* parent);
//鼠标 进入事件
void enterEvent(QEvent* event);
//鼠标 移出事件
void leaveEvent(QEvent* event);
};
#endif // LABEL_H
(9)重写 enterEvent() 和 leaveEvent() 方法
#include "label.h"
#include<QDebug>
Label::Label(QWidget* parent):QLabel(parent)
{
}
void Label::enterEvent(QEvent *event)
{
(void) event;
qDebug()<<"enterEvent";
//重写 虚函数
}
void Label::leaveEvent(QEvent *event)
{
(void) event;
qDebug()<<"leaveEvent";
}
(10)在 UI 文件中选中 Label,右键 ——> 提升为...
(11)当点击 “提升为...” 之后
通过 “提升为...” 这样的方式就可以把 Qt Designer 中托上去的控件的类型转换成自定义的控件类型。
(12)修改基类
(13)执行效果
当鼠标进入设计好的标签之后,就会在应用程序输出栏中打印:enterEvent;鼠标移出设计好的标签之后,就会在应用程序输出栏中打印:leaveEvent。
此时就说明当前的 enterEvent 和 leaveEvent 这两个事件就被我们给捕获到了。
【示例二 —— 当鼠标点击时,获取对应的坐标值】
(1)在 label.h 中声明 mousePressEvent() 方法
(2)在 label.cpp 中重写 mousePressEvent() 方法
鼠标左键点击时,打印对应的坐标值,鼠标右键点击时,打印基于屏幕的坐标。
void Label::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
qDebug() << "按下左键";
} else if (event->button() == Qt::RightButton) {
qDebug() << "按下右键";
}
// 当前 event 对象就包含了鼠标点击位置的坐标.
qDebug() << event->x() << ", " << event->y();
// globalX 和 globalY 是以屏幕左上角为原点, 获取的坐标.
qDebug() << event->globalX() << ", " << event->globalY();
}
实现效果如下:
3、键盘按键事件
Qt 中的按键事件是通过 QKeyEvent 类来实现的。当键盘上的按键被按下或者被释放时,键盘事件便会触发。
在帮助文档中查找 QKeyEvent 类如下:
查找按键事件中所有的按键类型:在帮助文档中输入:Qt::Key,如下图:
(1)单个按键
示例:当某个按键被按下时,输出:某个按键被按下了;
A. 新建项目,在头文件 "widget.h" 中声明虚函数 keyPressEvent();如下图:
B. 在 "widget.cpp" 文件中重写 keyPressEvent() 虚函数
(2)组合按键
在 Qt 助手中搜索:Qt::KeyboardModifier,如下图示:
Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改键。
在 Qt 中,键盘事件可以与修改键以起使用,以实现一些复杂的交互操作。
KeyboardModifier 中修改键的具体描述如下:
修饰键类型 | 描述 |
Qt::NoModifier | 无修改键 |
Qt::ShiftModifier | Shift 键 |
Qt::ControlModifier | Ctrl 键 |
Qt::AltModifier | Alt 键 |
Qt::MetaModifier | Meta键(在Windows上指Windows键,在macOS上指Command键) |
Qt::KeypadModifier | 使用键盘上的数字键盘进行输入时,Num Lock键处于打开状态 |
Qt::GroupSwitchModifier | 用于在输入法组之间切换 |
这些修饰键常用于编程中处理键盘事件,特别是在使用Qt框架开发跨平台应用程序时。
示例:
运行:
4、鼠标事件
在 Qt 中,鼠标事件是用 QMouseEvent 类来实现的。当在窗口中按下鼠标或者移动鼠标时,都会产生鼠标事件。
利用 QMouseEvent 类可以获取鼠标的哪个键被按下了以及鼠标的当前位置等信息。在 Qt 帮助文档中查找 QMouseEvent 类如下图示:
(1)鼠标单击事件
在 Qt 中,鼠标按下是通过虚函数 mousePressEvent() 来捕获的。mousePressEvent() 函数原型如下:
- [ virtual protected] void QWidget:: mousePressEvent (QMouseEvent * event )
鼠标左右键及滚的表示如下:
- Qt::LeftButton 鼠标左键
- Qt::RightButton 鼠标右键
- Qt::MidButton 鼠标滚轮
【鼠标左键/右键/滚轮】
A. 在 "widget.h" 头文件中声明鼠标按下事件
B. 在 "widget.cpp" 文件中重新实现 mousePressEvent() 函数
运行
- 在窗口中,按动鼠标,可以打印看到:
(2)鼠标释放事件
鼠标释放事件是通过虚函数 mouseReleaseEvent() 来捕获的。mouseReleaseEvent() 函数原型如下:
- [ virtual protected] void QWidget:: mouseReleaseEvent (QMouseEvent * event )
示例:
void Label::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
qDebug() << "释放左键";
} else if (event->button() == Qt::RightButton) {
qDebug() << "释放右键";
}
}
(3)鼠标双击事件
鼠标双击事件是通过虚函数:mouseDoubleClickEvent() 来实现的。mouseDoubleClickEvent() 函数原型如下:
- [ virtual protected] void QWidget:: mouseDoubleClickEvent (QMouseEvent * event )
示例:
void Label::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
qDebug() << "双击左键";
} else if (event->button() == Qt::RightButton) {
qDebug() << "双击右键";
}
运行
(4)鼠标移动事件
鼠标移动事件是通过虚函数:mouseMoveEvent() 来实现的。同时为了实时捕获鼠标位置信息,需要通过函数 setMouseTracking() 来追踪鼠标的位置。mouseMoveEvent()函数原型如下:
- [ virtual protected] void QWidget:: mouseMoveEvent (QMouseEvent * event )
setMouseTracking() 函数原型如下:
- void setMouseTracking( bool enable )
说明:setMouseTracking() 函数默认是 false,需要设置为 true,才能实时捕获鼠标位置信息。
否则只有当鼠标按下时才能捕获其位置信息。
示例:
执行效果:
(5)滚轮事件
在 Qt 中,鼠标滚轮事件是通过 QWheelEvent 类来实现的。滚轮滑动的距离可以通过 delta() 函数获取。delta() 函数原型如下:
- int QGraphicsSceneWheelEvent:: delta() const
其中返回值 代表滚轮滑动的距离。
正数表示滚轮相对于用户向前滑动,负数表示滚轮相对于用户向后滑动。
示例:
执行效果如下:
5、定时器
Qt 中在进行窗口程序的处理过程中,经常要周期性的执行某些操作,或者制作一些动画效果,使用定时器就可以实现。
所谓 定时器就是在间隔一定时间后,去执行某一个任务。
定时器在很多场景下都会使用到,如弹窗自动关闭之类的功能等。
Qt 中的定时器分为 QTimerEvent 和 QTimer 这 2 个类。
- QTimerEvent 类用来描述一个定时器事件。在使用时需要通过 startTimer() 函数来开启⼀个定时器,这个函数需要输入一个以毫秒为单位的整数作为参数来表明设定的时间,它返回的整型值代表这个定时器。
- 当定时器溢出时(即定时时间到达)就可以在 timerEvent() 函数中获取该定时器的编号来进行相关操作。
- QTimer 类来实现一个定时器,它提供了更高层次的编程接口,如:可以使用信号和槽,还可以设置只运行一次的定时器。
(1)QTimerEvent 类
联系前文:[Qt] 万字详解 | 常用控件 | Button | Label | LCD | ProgressBar
【示例 1】在 UI 界面上放置一个 LCD Number 控件,让其 10 秒数字不断递减到 0,相当于倒计时。
- A. 新建项目,在 UI 界面文件放置一个 LCD Number 控件
- B. 在 "widget.h" 头文件中声明 timerEvent() 函数,并定义一个整型变量
- C. 在 "widget.cpp" 文件中重写 timerEvent() 函数
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 开启定时器事件.
// 此处 timerId 是一个定时器的身份标识.
timerId = this->startTimer(1000);
}
Widget::~Widget()
{
delete ui;
}
void Widget::timerEvent(QTimerEvent *event)
{
// 如果一个程序中存在多个定时器 (startTimer 创建的定时器), 此时每个定时器都会触发 timerEvent 函数.
// 先判定一下这次触发是否是想要的定时器触发的.
if (event->timerId() != this->timerId) {
// 如果不是我们的定时器触发的, 就直接忽略.
// 当前程序中只有这一个定时器.
return;
}
int value = ui->lcdNumber->intValue();
if (value <= 0) {
// 停止定时器
this->killTimer(this->timerId);
return;
}
value -= 1;
ui->lcdNumber->display(value);
}
实现效果如下:
(2)QTimer 类
示例:在 UI 界面放置一个 Label 标签,两个按钮,分别是 “开始” 和 “停止”
当点击 “开始” 按钮时,开始每隔 1 秒计数一次,点击 “停止” 按钮时,暂停计数。
A. 设计 UI 界面
B. 在 "widget.cpp" 文件中实现对应功能
#include<QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QTimer* time=new QTimer(this);
connect(ui->btnl,&QPushButton::clicked,[=](){
time->start(1000);
});
connect(time,&QTimer::timeout,[=](){
static int num=1;
ui->label->setText(QString::number(num++));
});
connect(ui->btn2,&QPushButton::clicked,[=](){
time->stop();
});
}
实现效果如下:
(3)获取系统日期及时间
在 Qt 中,获取系统的日期及实时时间可以通过 QTimer 类和 QDateTime 类。
QDateTime 类提供了字符串格式的时间。字符串形式的时间输出格式由 toString() 方法中的 format 参数列表决定,可⽤的参数列表如下:
【获取系统日期及实时时间】
A. 设计 UI 界面文件
- 放置一个 Label 控件,用来显示日期及时间,放置两个按钮:“开始” 和 “停止”
B. 在 "widget.h" 头文件中声明更新时间的槽函数
C. 在 "widget.cpp" 文件中实现对应功能
//基于上一代码的 基础上
connect(time,&QTimer::timeout,this,&Widget::TimeUpDate);
}
void Widget::TimeUpDate()
{
QString str=QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
ui->label->setText(str);
}
实现效果如下:
6、事件分发器
(1)概述
在 Qt 中,事件分发器(Event Dispatcher)是一个核心概念,用于处理 GUI 应用程序中的事件。
事件分发器负责 将事件从一个对象传递到另一个对象,直到事件被处理或被取消。
每个继承自 QObject 类或 QObject 类本身都可以在本类中 重写 bool event(QEvent *e) 函数,来实现相关事件的捕获和拦截。
(2)事件分发器工作原理
在 Qt 中,我们发送的事件是传给了 QObject 对象的 event() 函数。
- 所有的事件都会进入到这个函数里面,那么我们处理事件就要重写这个 event() 函数。
- event() 函数本身不会去处理事件,而是根据 事件类型(type值)调用不同的事件处理函数。事件分发器就是工作在应⽤程序向下分发事件的过程中,如下图:
如上图,事件分发器用于分发事件。在此过程中,事件分发器也可以做拦截操作。
- 事件分发器主要是通过 bool event(QEvent *e) 函数来实现。其返回值为布尔类型,若为 ture,代表拦截,不向下分发。
- Qt 中的事件是封装在 QEvent类 中,在 Qt 助手中输入 QEvent 可以查看其所包括的事件类型,如下图示:
示例:
A. 在 "widget.h" 头文件中声明鼠标点击事件和事件分发器
如下图示:
B. 在 "widget.cpp" 文件中实现鼠标点击事件和拦截事件
执行结果如下:
7、事件过滤器
在 Qt 中,一个对象可能经常要查看或拦截另外一个对象的事件,如对话框想要拦截按键事件,不让别的组件接收到,或者修改按键的默认值等。
- 通过上面的学习,我们已经知道,Qt 创建了 QEvent 事件对象之后,会调用 QObject 的 event() 函数 处理事件的分发。
- 显然,我们可以在 event() 函数中实现拦截的操作。由于 event() 函数是 protected 的,因此,需要继承已有类。
- 如果组件很多,就需要重写很多个 event() 函数。这当然相当⿇烦,更不用说重写 event() 函数还得小心一堆问题。好在 Qt 提供了另外一种机制来达到这一目的:事件过滤器。
事件过滤器是在应用程序 分发到 event 事件分发器 之前,再做一次更高级的拦截。
如下图示:
事件过滤器的一般使用步骤:
- 安装事件过滤器;
- 重写事件过滤器函数:eventfilter() 。
示例:
(1)新建 Qt 项目
- 基类选择 QWidget,同时勾选 UI 界面文件
(2)设计 UI 文件
(3)在项目新添加⼀个类:MyLabel
先选中项目名称 QEvent,点击鼠标右键,选择 add new ... ,弹出如下对话框:
(4)选择:Choose ...
弹出如下界面:
(5)此时项目中会新添加以下两个文件
(6)在 UI 文件中选中 Label,右键 ——> 提升为...
(7)当点击 "提升为... " 之后,弹出对话框
(8)在 "mylabel.h" 中声明鼠标点击事件和事件分发器
(9)在 "mylabel.cpp" 文件中实现鼠标点击事件和事件分发器
#include "mylabel.h"
#include <QMouseEvent>
#include <QDebug>
myLabel::myLabel(QWidget *parent) : QLabel(parent)
{
}
void myLabel::mousePressEvent(QMouseEvent *event)
{
QString str = QString("鼠标按下: x = %1, y = %2").arg(event->x()).arg(event->y());
qDebug() << str.toUtf8().data();
}
bool myLabel::event(QEvent *e)
{
//如果是鼠标按下,在event事件分发时做拦截操作
if (e->type() == QEvent::MouseButtonPress)
{
QMouseEvent *event = static_cast<QMouseEvent *>(e);
QString str = QString("Event函数中鼠标按下: x = %1, y = %2").arg(event->x()).arg(event->y());
qDebug() << str.toUtf8().data();
return true; //返回true,代表用户自己处理,不向下分发
}
//其他事件交给父类处理
return QLabel::event(e);
}
(10)在 "widget.h" 头文件中声明事件过滤器函数
(11)在 "widget.cpp" 文件中实现事件过滤器的两个步骤
#include "widget.h"
#include "ui_widget.h"
#include <QMouseEvent>
#include <QDebug>
#include <QEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 步骤1: 给label安装事件过滤器 this:当前窗口安装事件过滤器
ui->label->installEventFilter(this);
}
bool Widget::eventFilter(QObject *obj, QEvent *e)
{
if (obj == ui->label) // 判断控件
{
if (e->type() == QEvent::MouseButtonPress)
{
QMouseEvent *event = static_cast<QMouseEvent *>(e);
QString str = QString("事件过滤器中鼠标按下: x = %1, y = %2").arg(event->x()).arg(event->y());
qDebug() << str.toUtf8().data();
return true; // 返回true,代表用户自己处理,不向下分发
}
}
// 其他的交给父类处理
return QWidget::eventFilter(obj, e);
}
执行结果如下所示: