文章目录
- 一、事件的接收和忽略
- 1. 准备工作
- 2. 接收和忽略
- 二、event() 函数
- 1. 简介
- 2. 实例演示
- 3. 总结
- 三、事件过滤器
- 四、总结(细看)
- 1. 知识点汇总
- 2. QT 的事件处理
- 五、事件、事件的接收和忽略、event() 函数和事件过滤器代码
- 1. 主窗口头文件 mywidget.h
- 2. 主窗口源文件 mywidget.cpp
- 3. 标签头文件 mylabel.h
- 4. 标签源文件 mylabel.cpp
- 5. 按钮头文件 mybutton.h
- 6. 按钮源文件 mybutton.cpp
由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见
QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见
QT 学习笔记(二)
一、事件的接收和忽略
- 在上文的代码基础上继续进行操作。QT 学习笔记(八)
1. 准备工作
- 在 ui 界面新建一个按钮,在项目文件当中新建一个 mybutton 的 C++ 类。
- 将 ui 界面的按钮提升为 mybutton 。自定义控件提升详见QT 学习笔记(七)。
- 在 主窗口源文件 mywidget.cpp 当中对按钮进行操作,对基本功能进行测验。
- 注意:要在项目文件 day8.pro 当中添加 CONFIG +=C++11 才可以使 connect 功能正常实现。
- 当我们点击按钮时,会在输出窗口输出按钮被按下,如下图所示:
2. 接收和忽略
- 在上一步的检测操作完成后,在按钮源文件 mybutton.cpp 当中,进行事件的接收和忽略操作。
- 通过 if 语句,对鼠标左键按下在输出窗口输出按下的是左键,对其他鼠标按键不做处理,调用原函数。
- 在上述操作当中,鼠标左键对应的就是事件的接收,其他按键对应的就是事件的忽略。
- 在这里有一个问题,当我们按下鼠标左键时,会输出按下的是左键,但当我们按下其他按键时,并没有调用原函数,即输出按钮被按下。
- 这是因为当我们按下鼠标左键的时候,事件的信号被 qDebug() 输出语句给拦截了,不会再向后传递,无法走到 else 对应的内容。
- 因此,在我们编写代码时,事件的忽略不要编写实现代码即可。
- 事件的接收,就不会往下传递。
- 事件的忽略,事件会继续往下传递。
- 我们可以把 QT 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其父组件传递。
- QT 的事件对象有两个函数:accept() 和 ignore() 。
- accept() 用来告诉 QT,这个类的事件处理函数想要处理这个事件;
- 如果一个事件处理函数调用了一个事件对象的 accept() 函数,这个事件就不会被继续传播给其父组件。
- ignore() 用来告诉 QT,这个类的事件处理函数不想要处理这个事件;
- 如果调用了事件的 ignore() 函数,QT 会从其父组件中寻找另外的接受者。在事件处理函数中,可以使 isAccepted() 来查询这个事件是不是已经被接收了。
- 我们在主窗口 myWidget.cpp 中,重写鼠标点击事件,每当鼠标按下的时候,输出 ++++++++++,同时,将按钮窗口 mybutton.cpp 当中的 e->ignore() 注释掉,得到如下现象:
- 这样操作并不会输出 ++++++++++。此时,我们取消按钮窗口 mybutton.cpp 当中的 e->ignore() 的注释,得到下面的现象:
- 当我们点击按钮的时候,会同时输出按下的是左键和 ++++++++++,当我们点击按钮外的界面时,只会输出 ++++++++++。
- 这是因为没有接收到信号,所以没有触发那里的 e->ignore()。而这里的 e->ignore() 可以让这个信号再次传递下去,不过不是传递给 mybutton,而是 父组件 mywidget。
- 这个 e->ignore() 主要使用在关闭项目当中,也就是窗口右上角大家熟知的红色关闭按钮,在此简单实现一下(只展示实现现象,具体代码会在总体代码当中展示)。
- 在我们实际关闭窗口的时候,往往会弹出一句 确定关闭吗? 这种类似的提示。
- 为了实现这样的功能,需要包含头文件 QMessageBox ,使用 question() 函数。question() 函数中的第一个参数是指定父对象,第二个参数是标题,第三个参数是提示内容,第四个参数不写的话默认有两个按钮:yes 和 no 。
- 为实现功能,只需要一个简单 if 语句即可,如果点击 yes ,则处理关闭窗口事件,接收事件,事件就不会再往下传递;如果点击 no ,则忽略事件,事件继续给父组件传递。
- 具体现象如下图所示:
二、event() 函数
1. 简介
- 事件对象创建完毕后,QT 将这个事件对象传递给 QObject 的 event() 函数。event() 函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。
- 因此,event() 函数主要用于 事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个 event() 函数了。
- 例如,我们希望在一个 QWidget 组件中监听 tab 键的按下,那么就可以继承 QWidget,并重写它的 event() 函数,来达到这个目的。
- 事件分发如下图所示。
- 与其他的事件返回值不同的是,event() 事件的返回值是 bool 类型。
- 如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,那么 QT 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象, 而是会继续处理事件队列中的下一事件。
- 在 event() 函数中,调用事件对象的 accept() 和 ignore() 函数是没有作用的,不会影响到事件的传播。
- 相当于在所有信号接收前进行一个检查,满足要求的停止工作,其他的继续原动作。
2. 实例演示
- 在此以定时器为例,重写 event() 函数,使得定时器停止工作,其他部件保持原动作。代码和实现结果如下:
//重写event事件
bool myWidget::event(QEvent *e)
{if(e->type()==QEvent::Timer){//干掉定时器//如果返回true,事件停止传播return true;}else{return QWidget::event(e);}
}
- 将其恢复正常需要一个强制类型转换,只需在上述代码当中添加如下代码即可:
QTimerEvent *env = static_cast<QTimerEvent *>(e);timerEvent(env);
- 正常现象如下图所示:
- 我们在此继续进行一个按键 B 的 event() 函数重写,只有当我们按下按键 B 的时候,事件才继续,其他的事件均终止。代码和实现结果如下:
else if(e->type()==QEvent::KeyPress){//类型转换QKeyEvent *env = static_cast<QKeyEvent *>(e);if(env->key()==Qt::Key_B){return QWidget::event(e);}return true;}
3. 总结
- QTimerEvent() 和 QKeyEvent() 这样的函数,就是前面所说的事件处理器 event handler。
- event() 函数中实际是通过事件处理器来响应一个具体的事件。这相当于 event() 函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 QT 调用我们自己实现的版本。
- event() 是一个集中处理不同类型的事件的地方。如果你想重写一大堆事件处理器,就可以重写这个 event() 函数,通过 QEvent::type() 判断不同的事件。由于重写 event() 函数需要十分小心注意父类的同名函数的调用,一不留神就可能出现问题。
- 因此,一般只重写事件处理器(当然,也必须记得是不是应该调用父类的同名处理器)。这其实也表明了 event() 函数的另外一个作用:屏蔽掉某些不需要的事件处理器。
三、事件过滤器
- 很多时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。
- 通过前文,我们已经知道,QT 创建了 QEvent 事件对象之后,会调用 QObject 的 event() 函数处理事件的分发。显然,可以在 event() 函数中实现拦截的操作。由于 event() 函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个 event() 函数。这当然相当麻烦,更不用说重写 event() 函数还得小心一堆问题。
- 因此,我们采用 QT 提供的另外一种机制来达到这一目的:事件过滤器。
- QObject 有一个 eventFilter() 函数,用于建立事件过滤器。函数原型如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
- 事件过滤器,可以理解成一种过滤代码。事件过滤器会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。
- 事件过滤器的调用时间是目标对象(也就是参数里面的 watched 对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched 对象以及以后所有的事件过滤器根本不会知道这么一个事件。
- 在实现事件过滤器之前,我们要在 ui 界面选取一个合适的标签,在这里选择的是 label_2 。 在选择完成后进行操作安装过滤器,该函数的参数是由哪个函数的父对象进行处理。
//安装过滤器
ui->label_2->installEventFilter(this);
- 通过事件过滤器,将 label_2 ,转换为鼠标移动,并实时显示出对应的 x 和 y 坐标,其他事件保持不变。(鼠标按下和释放的整体流程与鼠标移动是相同的,按钮的操作方法和标签也是相同的,具体代码在汇总时展示)
- 注意:事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
四、总结(细看)
1. 知识点汇总
- QT 的事件是整个 QT 框架的核心机制之一。涉及到的函数众多,而处理方法也很多。
- QT 中有很多种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件,实际有两种选择:
- (1) 所有事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择。
- (2) 每一种事件对应一个事件处理函数。QT 就是使用的这么一种机制:mouseEvent() ,keyPressEvent() 等。
- QT 具有这么多种事件处理函数,肯定有一个地方对其进行分发,否则,QT 怎么知道哪一种事件调用哪一个事件处理函数呢?这个分发的函数,就是 event()。显然,当 QMouseEvent 产生之后, event() 函数将其分发给 mouseEvent() 事件处理器进行处理。
- event() 函数会有两个问题:
- (1) event() 函数是一个 protected 的函数,这意味着我们要想重写 event() ,必须继承一个已有的类。试想,我的程序根本不想要鼠标事件,程序中所有组件都不允许处理鼠标事件,是不是我得继承所有组件,一 一重写其 event() 函数?protected 函数带来的另外一个问题是,如果我基于第三方库进行开发,而对方没有提供源代码,只有一个链接库,其它都是封装好的。我怎么去继承这种库中的组件呢?
- (2) event() 函数的确有一定的控制,不过有时候需求更严格一些:我希望那些组件根本看不到这种事件。event() 函数虽然可以拦截,但其实也是接收到了 QMouseEvent 对象。我连让它收都收不到。这样做的好处是,模拟一种系统根本没有那个事件的效果,所以其它组件根本不会收到这个事件,也就无需修改自己的事件处理函数。
- 这两个问题是 event() 函数无法处理的。于是,QT 提供了另外一种解决方案:事件过滤器。事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意 QObject 类型上面,并且可以安装多个。如果要实现全局的事件过滤器,则可以安装到 QApplication 或者 QCoreApplication 上面。这里需要注意的是,如果使用 installEventFilter() 函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件需要先传递给事件过滤器的 eventFilter() 函数进行过滤,其它对象不受影响。如果给 QApplication 对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给 eventFilter() 函数。
- 事件过滤器可以解决刚刚我们提出的 event() 函数的两点不足:
- (1) 事件过滤器不是 protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;
- (2) 事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过滤掉,目标对象根本不会见到这个事件。
2. QT 的事件处理
- QT 的事件处理,实际上是有五个层次:
- (1) 重写 paintEvent() 、mousePressEvent() 等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
- (2) 重写 event() 函数。event() 函数是所有对象的事件入口,QObject 和 QWidget 中的实现,默认是把事件传递给特定的事件处理函数。
- (3) 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
- (4) 在 QCoreApplication::instance() 上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和 notify() 函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
- (5) 重写 QCoreApplication::notify() 函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为 QCoreApplication 是单例的)。
五、事件、事件的接收和忽略、event() 函数和事件过滤器代码
1. 主窗口头文件 mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H#include <QWidget>namespace Ui {
class myWidget;
}class myWidget : public QWidget
{Q_OBJECTpublic:explicit myWidget(QWidget *parent = nullptr);~myWidget();protected://键盘按下事件void keyPressEvent(QKeyEvent *);//计时器事件void timerEvent(QTimerEvent *);//重写鼠标点击事件void mousePressEvent(QMouseEvent *);//关闭事件void closeEvent(QCloseEvent *);//重写event事件bool event(QEvent *);//事件过滤器bool eventFilter(QObject *,QEvent *);private:Ui::myWidget *ui;int timeid;int timeid2;
};#endif // MYWIDGET_H
2. 主窗口源文件 mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QDebug>
#include <QKeyEvent>
#include <QCloseEvent>
#include <QMessageBox>
#include <QEvent>myWidget::myWidget(QWidget *parent) :QWidget(parent),ui(new Ui::myWidget)
{ui->setupUi(this);//启动计时器timeid = this->startTimer(1000); //毫秒为单位。每隔1s触发一次定时器timeid2 = this->startTimer(500); //毫秒为单位。每隔0.5s触发一次定时器connect(ui->pushButton,&mybutton::clicked,[=](){qDebug()<<"按钮被按下";});//安装过滤器ui->label_2->installEventFilter(this);//设置鼠标追踪ui->label_2->setMouseTracking(tr);
}myWidget::~myWidget()
{delete ui;
}//键盘按下事件
void myWidget::keyPressEvent(QKeyEvent *e)
{qDebug()<<(char)e->key();if(e->key()==Qt::Key_A){qDebug()<<"Qt::Key_A";}
}//计时器事件
void myWidget::timerEvent(QTimerEvent *e)
{if(e->timerId()==this->timeid){static int sec = 0;ui->label->setText(QString("<center><h1>time out:%1</h1></center>").arg(sec++));//停止计时器/*if(5==sec){this->killTimer(this->timeid);} */}else if (e->timerId()==this->timeid2){static int sec = 0;ui->label_2->setText(QString("<center><h1>time out:%1</h1></center>").arg(sec++));//停止计时器/*if(5==sec){this->killTimer(this->timeid);} */}
}//重写鼠标点击事件
void myWidget::mousePressEvent(QMouseEvent *e)
{qDebug()<<"++++++++++";
}//重写鼠标点击事件
void myWidget::closeEvent(QCloseEvent *e)
{int ret = QMessageBox::question(this,"question","是否需要关闭窗口");//question 第一个参数是指定父对象,第二个参数是标题,//第三个参数是提示内容,第四个参数不写的话默认有两个按钮:yes和no。if(ret==QMessageBox::Yes){//关闭窗口//处理关闭窗口事件,接收事件,事件就不会再往下传递e->accept();}else{//不关闭窗口//忽略事件,事件继续给父组件传递e->ignore();}
}//重写event事件
bool myWidget::event(QEvent *e)
{if(e->type()==QEvent::Timer){//干掉定时器//如果返回true,事件停止传播//QTimerEvent *e//QTimerEvent *env = static_cast<QTimerEvent *>(e);//timerEvent(env);return true;}else if(e->type()==QEvent::KeyPress){//类型转换QKeyEvent *env = static_cast<QKeyEvent *>(e);if(env->key()==Qt::Key_B){return QWidget::event(e);}return true;}else{return QWidget::event(e);}
}//事件过滤器
bool myWidget::eventFilter(QObject *obj,QEvent *e)
{if(obj==ui->label_2){//类型转换QMouseEvent *env=static_cast<QMouseEvent *>(e);//判断事件if(e->type()==QEvent::MouseMove){ui->label_2->setText(QString("mouse move:(%1,%2)").arg(env->x()).arg(env->y()));return true;}else{return QWidget::eventFilter(obj,e);}}else{return QWidget::eventFilter(obj,e);}
}
3. 标签头文件 mylabel.h
#ifndef MYLABEL_H
#define MYLABEL_H#include <QLabel>class mylabel : public QLabel
{Q_OBJECT
public:explicit mylabel(QWidget *parent = nullptr);protected://鼠标点击事件void mousePressEvent(QMouseEvent *ev);//鼠标释放事件void mouseReleaseEvent(QMouseEvent *ev);//鼠标移动事件void mouseMoveEvent(QMouseEvent *ev);//进入窗口区域void enterEvent(QEvent *);//离开窗口区域void leaveEvent(QEvent *);signals:public slots:
};#endif // MYLABEL_H
4. 标签源文件 mylabel.cpp
#include "mylabel.h"
#include <QMouseEvent>mylabel::mylabel(QWidget *parent) : QLabel(parent)
{}//鼠标点击事件
void mylabel::mousePressEvent(QMouseEvent *ev)
{int i=ev->x();int j=ev->y();//sprinf 字符串格式化命令/** QString str = QString("abc %1 ^_^ %2").arg(123).arg("mike");* str = abc 123 ^_^ mike*/QString text = QString("<center><h1>mouse press:(%1,%2)</h1></center>").arg(i).arg(j);// center 居中,h1 一级标题this->setText(text);
}//鼠标释放事件
void mylabel::mouseReleaseEvent(QMouseEvent *ev)
{QString text = QString("<center><h1>mouse release:(%1,%2)</h1></center>").arg(ev->x()).arg(ev->y());this->setText(text);
}//鼠标移动事件
void mylabel::mouseMoveEvent(QMouseEvent *ev)
{QString text = QString("<center><h1>mouse move:(%1,%2)</h1></center>").arg(ev->x()).arg(ev->y());//this->setText(text);
}//进入窗口区域
void mylabel::enterEvent(QEvent *e)
{QString text = QString("<center><h1>mouse enter</h1></center>");this->setText(text);
}//离开窗口区域
void mylabel::leaveEvent(QEvent *e)
{QString text = QString("<center><h1>mouse leave</h1></center>");this->setText(text);
}
5. 按钮头文件 mybutton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H#include <QPushButton>class mybutton : public QPushButton
{Q_OBJECT
public:explicit mybutton(QWidget *parent = nullptr);protected:void mousePressEvent(QMouseEvent *e);signals:public slots:
};#endif // MYBUTTON_H
6. 按钮源文件 mybutton.cpp
#include "mybutton.h"
#include <QMouseEvent>
#include <QDebug>mybutton::mybutton(QWidget *parent) : QPushButton(parent)
{}void mybutton::mousePressEvent(QMouseEvent *e)
{if(e->button() == Qt::LeftButton){//如果是左键按下qDebug()<<"按下的是左键";//事件接收后,就会往下传递e->ignore();//忽略,事件继续往下传递,事件传递给了父组件,不是给父类(基类)}else{//不做处理QPushButton::mousePressEvent(e);//事件的忽略,事件继续往下传递}
}