【Qt开发】事件
目录
1,事件介绍
2,事件的处理
2-1,鼠标事件
2-2,按键事件
2-3,定时器
2-4,事件分发器和事件过滤器
1,事件介绍
事件与信号槽非常相似,它是应用程序内部或者外部产生的事情或者动作的统称。用户进行的各种操作,就会产生对应的事件。这里与信号槽一样,我们同样可以给事件关联上处理函数,以便事件触发时能够找到对应的函数处理。
Qt对于事件机制又进行了进一步的封装,就成为了信号槽。也就是说,信号槽就是对于事件的进一步封装,事件是信号槽的底层机制。
在Qt中使用一个对象来表示⼀个事件。所 有的Qt事件均继承于抽象类QEvent。事件是由系统或者Qt平台本身在不同的时刻发出的。当用户按 下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件是在用户操作时发出,如键盘事件、鼠标事件等,另⼀些事件则是由系统本身自动发出,如定时器事件。常见的Qt事件如下:
常见事件描述如下:
2,事件的处理
事件处理一般常用的方法为:重写相关的 Event 函数。
在Qt中,几乎所有的 Event 函数都是虚函数,所以可以重新实现。如:在实现鼠标的进入和离开事件时,重写 enterEvent() 和 leaveEvent() 即可。
事件的处理是在具体控件上实现的。每个控件可以有相同的事件,如鼠标点击。
2-1,鼠标事件
鼠标事件是用 QMouseEvent类来实现的。当在窗口中按下鼠标或者移动鼠标时,都会产生鼠标事件。利用 QMouseEvent类可以获取鼠标的哪个键(Qt::LeftButton鼠标左键、Qt::RightButton 鼠标右键、Qt::MidButton 鼠标滚轮)被按下了以及鼠标的当前位置等信息。
下面来用具体的代码说明事件处理的流程,如下图:
下面来模拟实现鼠标相关的事件。
// 重写特定控件的事件处理虚函数
// 对label标签鼠标事件的虚函数进行重写
// label.h中自定义Label子类,声明相关的事件虚函数
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
#include <QDebug>
class Label : public QLabel
{
Q_OBJECT
public:
Label(QWidget* parent = nullptr);
// 鼠标进入区域时触发的事件函数
// 鼠标相关事件的event参数包含了有关鼠标事件的信息
void enterEvent(QEvent* event);
// 鼠标离开区域时触发的事件函数
void leaveEvent(QEvent* event);
// 该事件处理函数用于处理鼠标按下事件(左右键和滚轮都会触发)
void mousePressEvent(QMouseEvent* event);
// 该事件处理函数用于处理鼠标释放事件(左右键和滚轮都会触发)
void mouseReleaseEvent(QMouseEvent* event);
// ⿏标双击事件的虚函数
void mouseDoubleClickEvent(QMouseEvent* event);
// ⿏标移动事件的虚函数
void mouseMoveEvent(QMouseEvent* event);
// 鼠标滚轮事件的虚函数
void wheelEvent(QWheelEvent* event);
};
// label.cpp 实现虚函数的重写
Label::Label(QWidget* parent) : QLabel(parent)
{
// setMouseTracking用于设置鼠标追踪。设置为true表示启动鼠标移动追踪
this->setMouseTracking(true); // 该函数默认参数是false
}
void Label::enterEvent(QEvent* event)
{
qDebug() << "鼠标进入此区域";
}
void Label::leaveEvent(QEvent* event)
{
qDebug() << "鼠标离开此区域";
}
void Label::mousePressEvent(QMouseEvent* event)
{
if(event->button() == Qt::LeftButton) {
qDebug() << "按下左键";
} else if (event->button() == Qt::RightButton) {
qDebug() << "按下右键";
}
// event对象包含了鼠标点击位置的坐标(x,y)
// 以label控件为起点的坐标
qDebug() << event->x() << ", " << event->y();
// 以整个窗口为起点的坐标
qDebug() << event->globalX() << ", " << event->globalY();
}
void Label::mouseReleaseEvent(QMouseEvent* event)
{
if(event->button() == Qt::LeftButton) {
qDebug() << "释放左键";
} else if (event->button() == Qt::RightButton) {
qDebug() << "释放右键";
}
}
void Label::mouseDoubleClickEvent(QMouseEvent* event)
{
if(event->button() == Qt::LeftButton) {
qDebug() << "双击左键";
} else if (event->button() == Qt::RightButton) {
qDebug() << "双击右键";
}
}
void Label::mouseMoveEvent(QMouseEvent* event)
{
qDebug() << event->x() << event->y();
}
void Label::wheelEvent(QWheelEvent* event)
{
static int x = 0;
// 滚轮滑动的距离可以通过delta()函数获取
x += event->delta();
qDebug() << event->delta() << "总距离: " << x;
if (event->delta() > 0) {
qDebug() << "滚轮向前滑动";
} else {
qDebug() << "滚轮向后滑动";
}
}
// 注意:这里是在ui界面上创建的label,因为Label是自定义类,所以需要把界面上label控件提升(鼠标右键点击控件,设置提升的类名)
2-2,按键事件
按键事件的实现与上类似,模拟代码如下:
// 在窗口Widget重写键盘事件的虚函数
// 按键事件的虚函数
void Widget::keyPressEvent(QKeyEvent* event)
{
// key()表示按下键的值。modifiers()获取在触发键盘事件时被按下的修饰键(如Shift键、Ctrl键)
if (event->key() == Qt::Key_A && event->modifiers() == Qt::ControlModifier) {
qDebug() << "按下了ctrl + A 键";
}
qDebug() << event->key();
}
这里说明下按键事件中的组合键 Qt::KeyboardModifier。Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改键。在 Qt 中,键盘事件可以与修改键⼀起使用,以实现⼀些复杂的交互操作。KeyboardModifier 中修改键的具体描述如下:
2-3,定时器
定时器(Timer)是一种用于在指定时间间隔后触发事件的机制。Qt提供了一种简单的方式来使用定时器,既可以使用类QTimer,也可以使用QObject的成员函数startTimer()。
使用QTimer:
QTimer是一个方便的类,可以用来设置一次性或重复性的定时器。基本使用步骤如下:
- 创建QTimer实例:首先需要创建一个QTimer对象。
- 连接timeout()信号到槽:当定时器超时时,它会发出timeout()信号。这里可以将这个信号连接到你的槽函数上,以执行特定的操作。
- 启动定时器:通过调用start(int msec)方法来启动定时器,并指定时间间隔(毫秒)。
- 停止定时器:可以通过调用stop()方法来停止定时器。
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Widget::yourSlotFunction);
timer->start(1000); // 每隔1000毫秒(即1秒)触发一次
使用startTimer():
如果你想要更底层的控制或者不想使用QTimer,可以重写QObject::timerEvent(QTimerEvent *event)
方法来处理定时器事件。这涉及到调用 startTimer 来启动一个定时器,killTimer 关闭定时器,并实现 timerEvent()函数来定义定时器触发时的行为。
- 启动定时器:调用
startTimer(int interval)
,返回一个定时器ID,用于标识该定时器。 - 重写timerEvent()函数:在这个函数中处理定时器事件。
// widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void timerEvent(QTimerEvent* event);
private:
Ui::Widget *ui;
int timerId;
};
// widget.cpp
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函数
// 先判定一下这次触发是否是想要的定时器触发的。此处的timerld类似于文件描述符标识
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-4,事件分发器和事件过滤器
事件分发器:
事件分发器是负责处理和分发事件到各个对象的核心组件。它确保了所有生成的事件能够被正确地发送到目标QWidget或其他QObject派生类的对象上。
Qt中,我们发送的事件都是传给了 QObject对象,更具体点是传给了 QObject对象的 event()函数。所有的事件都会进入到这个函数里面,那么我们处理事件就要重写这个 event()函数。event() 函数本身不会去处理事件,而是根据事件类型(type值)调用不同的事件处理函数。事件分发器就是工作在应用程序向下分发事件的过程中。
bool event(QEvent* e); // 若返回ture,代表拦截,即不向下分发
事件过滤器:
事件过滤器用于过拦截事件。事件分发器用于分发事件。在此过程中,事件分发器也可以做拦截操作。事件过滤器是在应用程序分发到 event事件分发器之前,再做一次更高级的拦截。
事件过滤器的一般使用步骤:1、安装事件过滤器;2、重写事件过滤器函数:eventfilter()。
事件分发过程:
事件首先被创建;然后看是否使用了事件过滤器,若使用了事件将会在此拦截掉;若没有使用,最后会传递给事件分发器进行下一步处理。事件分发器会根据事件的目标对象以及事件的类型决定如何处理该事件。以下是事件分发的一般流程:
- 当发生某个事件时,Qt创建一个对应的事件对象。
- 这个事件对象随后被传递给事件分发器。
- 事件分发器查找与事件关联的目标对象,并检查这个对象是否安装了事件过滤器。
- 如果有安装事件过滤器,事件首先被发送到过滤器的
eventFilter()
方法进行处理。 - 如果事件过滤器未处理该事件或者没有安装事件过滤器,事件将被发送到目标对象的
event()
方法。 event()
方法默认实现了对不同事件类型的分发逻辑,可以将其重写以自定义事件处理行为。- 最终,具体的事件处理函数(如
mousePressEvent()
等)被调用来处理事件。
如下图示:
Qt中的事件是封装在 QEvent类中,在Qt助手中输入 QEvent 可以查看其所包括的事件类型,如下图示:
代码示例如下:
// 事件分发器
// mylabel.h自定义文件
#include <QLabel>
#include <QDebug>
#include <QMouseEvent>
class myLabel : public QLabel
{
Q_OBJECT
public:
myLabel(QWidget* parent = nullptr);
// 鼠标点击事件
void mousePressEvent(QMouseEvent* event);
// 事件分发器
bool event(QEvent* e);
};
// mylabel.cpp自定义文件
myLabel::myLabel(QWidget* parent) : QLabel(parent)
{ }
void myLabel::mousePressEvent(QMouseEvent* event)
{
qDebug() << "鼠标按下";
qDebug() << event->x() << ", " << event->y();
}
bool myLabel::event(QEvent* e)
{
// 如果是鼠标按下,在事件分发时做拦截
if (e->type() == QEvent::MouseButtonPress)
{
qDebug() << "鼠标按下事件被拦截";
return true;
}
// 其它事件交给父类正常处理,即默认处理
return QLabel::event(e);
}
// 事件过滤器
// widget.h头文件中
// 重写事件过滤器函数
bool eventFilter(QObject* obj, QEvent* event);
// widget.cpp中
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 给label控件安装事件过滤器,并且该事件过滤器由当前对象(this)提供
ui->label->installEventFilter(this);
}
Widget::~Widget()
{
delete ui;
}
bool Widget::eventFilter(QObject* obj, QEvent* event)
{
if (obj == ui->label)
{
if (event->type() == QEvent::MouseButtonPress)
{
qDebug() << "label控件的鼠标按下事件被过滤";
return true;
}
}
// 其它控件的事件默认处理
return QWidget::eventFilter(obj, event);
}
注意:一个事件过滤器对象可以监听多个目标控件的事件。,比如在上面代码基础上加上 ui->lineEdit->installEventFilter(this); 往往需要在 eventFilter 函数中做出相应的事件判断处理。