事件的传递和处理
当一个事件发生时,Qt通过构造一个QEvent对象来表示发生的事件,然后通过调用event()函数,将该事件对象发送给特定的QObject(或其子类)对象。
此函数不会对事件本身进行处理, 而是首先检查所接受到的事件类型, 然后根据事件类型来调用相应的事件处理程序,事件处理程序在处理完事件之后会返回一个bool值表示该事件是被接受,还是被忽略了。
具体流程:
一个事件发生(如按键被按下),先是检查QApplication的事件过滤器,然后进入QApplication::notify中,过滤或合并一些事件,之后进入receiver中。
进入receiver之后,也是先进入receiver自身的filter,有的事件被filter处理了,有的被filter忽略了,那么就会执行它的父类的事件处理函数(若父类也忽略,继续往上,直到最上层)。直至最后,还被filter未处理的事件,会传递到event中,event根据事件类型进行派发,将事件派发到特定的事件处理函数中。
其实事件的处理可以分为:先进入QApplication的notify=(若有)继承自QApplication的自定义类的notify->QApplication的filter->receiver的filter->receiver的event->具体的event handler。
自定义事件
//继承QEvent,包含一系列需要的数据,并要提供给其特定的event type
class MyEvent : public QEvent
{
public:
MyEvent();
MyEvent(int x, int y, int z);
static const Type type;
int x;
int y;
int z;
};
const QEvent::Type MyEvent::type = (QEvent::Type)QEvent::registerEventType()//注册自定义事件,后续判断就用这个返回的type
//对于自定义事件,必须要重写event或其他对其处理
发送事件
先说明QT的事件循环机制,就是在main函数中的QApplication.exec()执行时开始,不断执行processEvent获取事件,处理并删除事件(使用的是delete)。
1 send
直接发送到指定对象,不放入事件循环队列中,直接调用notify函数将事件派发,send的event对象必须分配在stack上(局部变量),因为send不会自动删除事件
QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0);//可以是qt自带事件,也可以是自定义事件
QApplication::sendEvent(mainWin, &event);//绕过事件循环,直接派发到指定对象,返回一个bool表示事件是否处理完毕
2 post
直接添加到事件循环队列中,并自动删除事件,但event对象必须是手动new出来的
QApplication::postEvent(object, new MyEvent(QEvent::registerEventType(2048)));//将事件添加到事件循环队列中,并立即返回
3 sendpost
将事件循环队列中的指定type的事件,立刻马上派发给指定对象
4 accept和ignore
一般只用于处理事件的最终的具体的handler中
void MyFancyWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Key_Escape) {
...
event->accept();//告诉QT此事件已处理完毕,不必再派发
} else {
event->ignore();//若ignore,则qt会继续往上(父类)中寻找事件的handler
}
}
//但accept和ignore最好是用filter等更高级的方法来代替,如filter中return true和false
//一般event都默认为accepted(即不显示调用accept也会停止派发),所以一般不显示调用accept,而只是在需要忽略时调用ignore,或是不ignore而转向执行其他处理函数
//而且QWidget中所有handler都是默认ignore的,所以忽略时也可以直接转向QWidget的默认handler
事件派发与处理
- notify
QApplication的notify作为最高层,在事件触发时,第一个接到事件对象并对其处理,是全局的。
但作为内置类,它的notify不能被改写,如果我们需要在第一时间处理事件,就可以对QApplication安装filter(但仍晚于notify),或是继承它并重写notify,然后使用这个自定义类作为QApplication的替代,在main函数中创建整个应用程序。
步骤:①继承QApplication,仅重写其notify函数(若其他也要自定义则自行修改)
②在main函数中,一般创建应用程序都使用的是QApplication,但重写后要使用我们自定义的类来替代
补充:窗口并非就是应用程序,它们是父子关系,一个程序可以有多个窗口,但每个窗口都属于这一个程序。
- filter
为何使用filter:由于若要自定义某事件的处理函数,那一般的做法就是继承父类(如QLabel)
从事件的传递和处理顺序,我们知道了事件过滤触发的时机,但它的具体作用如下:
每个特定的对象,都有一个指定的事件过滤器(filter),这个filter通过它自身的eventFilter()函数来进行过滤(说是过滤,其实就是第一层处理)。当事件发生时,首先就是进入QObject的eventFilter中,对特定类型的事件作出处理,然后再按照顺序往下传递。
//一个典型的filter
bool MyFilter::eventFilter(QObject *obj, QEvent *event)
{
if(obj == ui->label)//obj为事件所触发的对象(不是filter!如btn1被按住时,obj就是btn1)
{
if (event->type() == QEvent::Enter)//通过event参数的type来判断触发的事件
{
ui->label->setText("我是红色");
ui->label->setStyleSheet(redStyle);
return true;//eventFilter返回true,表示filter对事件已作出处理,事件不再向下传递
}
else if(event->type() == QEvent::Leave)
{
ui->label->setText("我是黑色");
ui->label->setStyleSheet(blackStyle);
return true;
}
return false;//返回false,事件继续向下传递(传递到receiver的event函数中)
}
return QWidget::eventFilter(obj, event);//或是这样写,让其他类(一般都是父类)来对事件进行处理
}
安装filter
MyFilter f;
Watched w;
w.installEventFilter(f);//为w安装f作为filter,每次有事件传递到w对象时,会先进入到f的eventFilter函数中
w.removeEventFilter(f);//解除filter的监听
一般使用方式:① 特定对象使用特定filter
② 对QApplication安装自定义filter,实现全局事件过滤(最先处理,如实现软件中的全局快捷键)
③ 对于包含多个组件的父窗口类,定义一个eventFilter函数来处理多个组件的事件,或是指定特定的fitler作为这多个组件的filter(不然就需要每个组件都要继承并重写自身的事件处理函数)
其他:① 当一个对象安装有多个filter时,会逐个执行,但顺序为先安装的后调用
③ 也可以在一个对象中定义eventFilter函数,然后指定对象自身为它自己的filter
- event
最简单粗暴,也是优先级最低的方法,就是继承一个类并重写它的event函数
其大致类似于filter,算是优先级更低版本的filter
//一个典型的event
bool MyWidget::event(QEvent *e)//此参数为待派发的事件对象
{
//关于return:事件的处理是一个一个挨着来的,第一个event处理完后return true表示event1处理完毕
//然后event2再进来,return false表示未处理,继续向下传递
//然后event3进来,return一个函数,则表示传递给其他对象来处理
if (e->type() == QEvent::KeyPress)//同样可以通过type判断触发的事件
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);//若要使用event对象,通常需要cast
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;//返回true,表示此事件处理完毕,不会再往下派发
}
}
return QWidget::event(e);//每个event函数都必须要写,不然会出现事件无法处理的情况
}
若还要更粗暴,就是直接继承并重写event handler
//一个典型的handler
void myLabel::mousePressEvent(QMouseEvent *event)//因为已经是最后一层,event的类型已然明了
{
//作最终的事件处理
if(event->Buttons == LeftButton)
{
//do sth
}
else if(event->Buttons == RightButton)
{
//do sth
}
}