事件过滤
event()函数是一个protected的函数,这意味着我们要想重写event(),必须继承一个已有的组件类,——重写其event()函数。event()函数的确有一定的控制,不过有时候我的需求更严格一些:我希望那些组件根本看不到这种事件。event()函数虽然可以拦截,但其实也是接收到了事件。我连让它收都收不到。这样做的好处是,模拟一种系统根本没有那个事件的效果,所以其它组件根本不会收到这个事件,也无需修改自己的事件处理函数。所以我们可以使用事件过滤器,事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意QObject类型上面,并且可以安装多个。
我们需要用到2个函数:
QObject::installEventFilter:安装过滤器
void installEventFilter(QObject *filterObj)
filterObj:监控者,包含eventFilter事件过滤器的对象,当this发生事件时,会先执行filterObj对象中的过滤器,再分发事件。
QObject::eventFilter:过滤器函数
virtual bool eventFilter(QObject *watched, QEvent *event);
watch被过滤器监视的对象,event:发生的事件,当watched对象发生事件时,会先调用过滤,在进行event()分发。
返回true代表,拦截成功,事件将不会再继续传递。
返回false代表,放行。
函数执行顺序:eventFilter(事件过滤) -> event(事件分发) -> event Handler(具体事件处理器)。
例子:在窗口界面上添加line edit 和 plain text edit 组件并用label约定为密码和描述。
针对于密码输入组件,我们规定只能输入字母,对描述的内容,我们约定滑轮中键按下且滚动时,为放大或缩小文本。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eunTemw5-1688529688054)(C++.assets/image-20230630230844290.png)]](https://img-blog.csdnimg.cn/6edcb03dabf44aa7b11735ecaca820e2.png)
对于上述要求,我们使用事件的重写,分发完全可以做到,但是需要我们自定义类、继承组件并提升。如果界面的组件有很多,每一个都需要自定义组件将导致增加很多类,带来代码管理上的麻烦。
首先我们在主窗口cpp中安装对应的过滤器
//安装过滤器,参数:由谁监控(事件集中处理者)
ui->lineEdit_pass->installEventFilter(this);
ui->plainTextEdit->installEventFilter(this);
然后在QObject类中找到过滤器函数
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3jxWen2O-1688529688055)(C++.assets/image-20230630231307501.png)]](https://img-blog.csdnimg.cn/8e096c441def4bf38de49d447db101da.png)
拿到主窗口类中进行重写
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7xDBzWEc-1688529688055)(C++.assets/image-20230630231338206.png)]](https://img-blog.csdnimg.cn/d1555c5a39bf42bdb3f64aa0d7b539a5.png)
定义:
//watched:发生了某个事件的对象,event: 发生了什么事件
bool MainWindow::eventFilter(QObject *watched, QEvent *event){
if(watched == ui->lineEdit_pass){ //如果密码输入框发生了事件
if(event->type() == QEvent::KeyPress){
QKeyEvent *pKey = (QKeyEvent*) event; //强转为具体的键盘事件
if(Qt::Key_A <= pKey -> key() && pKey -> key() <= Qt::Key_Z){ //如果是字母,不拦截
qDebug()<<"放行:" <<pKey->key();
}else{ //拦截
qDebug()<<"拦截:" <<pKey->key();
return true; //
}
}
}else if(watched == ui->plainTextEdit){
}
return QMainWindow::eventFilter(watched,event); //调用父类的拦截函数(放行)
}
然后我们可以在设计窗口中将文本输入框中的输入方式改为密码输入
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3FPDiVLM-1688529688055)(C++.assets/image-20230630231622991.png)]](https://img-blog.csdnimg.cn/ff8bd08d9fee47e5ab8ac2ab4cf40c49.png)
这样我们在输入时密码就不会显示出来了
运行效果:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aA36hLjG-1688529688056)(C++.assets/image-20230630231725968.png)]](https://img-blog.csdnimg.cn/b8d167c951224722bce7e7be20acf1e3.png)
当我们输入英文字母时就会放行,输入其他时就会拦截
下面再来实现按下鼠标中间并滚动去放大缩小说明中的字体
else if(watched == ui->plainTextEdit){
if(event->type() == QEvent::Wheel){ //如果是滑轮事件
QWheelEvent * pWheel = (QWheelEvent *)event;
if(pWheel->buttons() == Qt::MiddleButton){ //如果鼠标中建按下
qDebug() << "x= " << pWheel->angleDelta().x(); //Alt+滑轮上滚动 +120 下 -120
qDebug() << "y= " << pWheel->angleDelta().y(); //滑轮上滚动 +120 下 -120
//1:8 -> 滚动15读
if(pWheel->angleDelta().y() > 0){
ui->plainTextEdit->zoomIn(); //放大
}else{
ui->plainTextEdit->zoomOut(); //缩小
}
}
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A88JlAAB-1688529688056)(C++.assets/image-20230701133919077.png)]](https://img-blog.csdnimg.cn/ecc09cec28c947848a00773d59e85e30.png)
自定义事件
我们在界面上添加两个spin box用作计算的两个数,combo box作为计算的规则,push button将计算的结果以事件的形似发送出去。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8DlQ5x27-1688529688056)(C++.assets/image-20230701135818216.png)]](https://img-blog.csdnimg.cn/f3a8a4a1ace14a90943a5d99a83ef9ba.png)
首先我们在主窗口的构造中添加好四种规则
ui->comboBox->addItems(QStringList{"+","-","*","/"});
然后将计算按钮转到槽,然后在槽函数中获取数值
void MainWindow::on_pushButton_2_clicked()
{
//获取数值
int v1 = ui->spinBox1->value();
int v2 = ui->spinBox2->value();
QString str = ui->comboBox->currentText();
}
在之后我们去自定义一个类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNZtPIg3-1688529688057)(C++.assets/image-20230701140349307.png)]](https://img-blog.csdnimg.cn/edf4f353e1e745a5a94447667f5a79b6.png)
将继承父类修改为QEvent,构造函数参数改为事件的类型,添加事件携带的信息
类头文件:
#ifndef MYEVENT_H
#define MYEVENT_H
#include <QEvent>
#include <QString>
class MyEvent : public QEvent
{
//Q_OBJECT
public:
explicit MyEvent(Type type);
int m_v1;
int m_v2;
QString m_rule;
};
#endif // MYEVENT_H
类源文件:
#include "myevent.h"
MyEvent::MyEvent(Type type) : QEvent(type),m_v1(0),m_v2(0)
{
}
在槽函数中定义事件对象并填充数据,在槽函数外要定义个全局的自定义事件类型
//自定义事件类型:User ~ MaxUser
QEvent::Type myEventType = QEvent::User;
MyEvent eve(myEventType); //定义事件对象
//填充数据
eve.m_rule = str;
eve.m_v1 = v1;
eve.m_v2 = v2;
然后我们要发送自定义事件,但是没有接收窗口
所以我们创建一个dialog窗口
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wSaeAu8e-1688529688057)(C++.assets/image-20230701161558492.png)]](https://img-blog.csdnimg.cn/5454ab7f4fc7498f816d741d114e3951.png)
在这个窗口中加入label组件
之后我们去main文件中创建dialog窗口的对象,但是我们在这个窗口创建的对象并不能在槽函数中使用,所以我们在全区位置创建一个该类型的指针指向这个对象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u0bm8jVI-1688529688057)(C++.assets/image-20230701161806061.png)]](https://img-blog.csdnimg.cn/f4287c29736b40bba6b8cb34b44a67eb.png)
这样我们在槽函数就可以使用这个窗口作为接收窗口了,不要忘了包含dialog的头文件
extern Dialog* pdia; //声明
QCoreApplication::sendEvent(pdia,&eve); //发送自定义事件
我们在QObject类中找到用户事件函数
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h9BS697v-1688529688057)(C++.assets/image-20230701162139768.png)]](https://img-blog.csdnimg.cn/729a922ba43d40ceae633acc0ddd8863.png)
将它拿到dialog类中进行重写定义
在这个函数中实现对数据的加减乘除并显示
void Dialog::customEvent(QEvent *event){
extern QEvent::Type myEventType;
if(event->type() == myEventType){ //如果是自定义事件
MyEvent* myEve = (MyEvent*)event; //强转为自定义的事件
int ret = 0;
if(myEve->m_rule == "+"){
ret = myEve->m_v1 + myEve->m_v2;
}else if(myEve->m_rule == "-"){
ret = myEve->m_v1 - myEve->m_v2;
}else if(myEve->m_rule == "*"){
ret = myEve->m_v1 * myEve->m_v2;
}else if(myEve->m_rule == "/"){
ret = myEve->m_v1 / myEve->m_v2;
}
ui->label->setText(QString("%1 %2 %3 = %4").arg(myEve->m_v1).arg(myEve->m_rule).arg(myEve->m_v2).arg(ret));
this->show(); //显示窗口
}
}
这样就可以了
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N4YAHwZJ-1688529688058)(C++.assets/image-20230701162441888.png)]](https://img-blog.csdnimg.cn/52dad0116d814ee3898e47aa12e7a918.png)
刚才我们用的是sendEvent函数发送事件
现在我们换一种方式发送,postEvent
经过测试我们发现send会阻塞而post为非阻塞
MyEvent *pEve = new MyEvent(myEventType);
pEve->m_rule = str;
pEve->m_v1 = v1;
pEve->m_v2 = v2;
QCoreApplication::postEvent(pdia,pEve); //发送短信,非阻塞
现在我们想让结果显示3秒之后窗口自动去关闭
所以我们还要去用一下定时器事件,因为是要关闭dialog窗口,所以在dialog源文件中创建定时器,参数为3000毫秒,他会返回一个定时器id,所以我们要在类成员属性中创建一个
int m_timerId;
//设定定时器
m_timerId = this->startTimer(3000);
接下来就是对定时器事件进行实现了,还记得我们找用户事件处理函数时上边有一个定时器处理函数,都是在QObject类中
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iHb2iH1i-1688529688058)(C++.assets/image-20230701165037865.png)]](https://img-blog.csdnimg.cn/a27f1e5355f148d79ecb5c1076c22284.png)
我们还是将这个函数拿到dialog类中进行重写
void Dialog::timerEvent(QTimerEvent *event){
if(event->timerId() == m_timerId){ //我设定的定时器除法了
this->hide();
this->killTimer(m_timerId); //停止定时器
}
}
这样就可以了