qt事件原理

Qt事件原理

Qt 事件原理

本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)

若对C++语法不熟悉,建议参阅《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。

2.6.1 QApplication、QGuiApplication、QCoreApplication简介

继承顺序为QObject、QCoreApplication、QGuiApplication、QApplication(左侧为顶级父类)
一个程序中只能有一个QCoreApplication及其子类的对象,该类主要提供无GUI程序的事件循环。QGuiApplication用于管理GUI程序的控制流和主要设置。
QApplication类专门为QGuiApplication提供基于QWidget的程序所需的一些功能,主要用于处理部件的初始化、最终化,主要职责如下:
 使用用户的桌面设置初始化应用程序。
 执行事件处理,也就是说该类能从底层系统接收并分发事件。比如,使用QCoreApplication::sendEvent()或QCoreApplication::postEvent()函数分发自定义事件。
 解析常用命令行参数并设置其内部状态。
 定义了应用程序的界面外观,可使用QApplication::setStyle()进行更改。
 指定应用程序如何分配颜色。
 使用QCoreApplication::translate()函数对字符串进行转换。
 通过QApplication::desktop()函数处理桌面,通过QCoreApplication::clipboard()函数处理剪贴板。
 管理应用程序的鼠标光标。比如使用QGuiApplication::setOverrideCuresor()函数设置光标等。

2.6.2 Qt对事件的描述及分类

事件是由程序内部或外部产生的事情或某种动作的通称。比如用户按下键盘或鼠标,就会产生一个键盘事件或鼠标事件(这是由程序外部产生的事件);再如,当窗口第一次显示时,会产生一个绘制事件,以通知窗口需要重新绘制其自身,从而使该窗口可见(这是由程序内部产生的事件)。
事件和信号的区别
 他们两个是不同的概念,不要弄混淆。信号是由对象产生的,而事件则不一定是由对象产生的(比如由鼠标产生的事件),事件通常来自底层的窗口系统,但也可以手动发送自定义的事件,可见信号和事件的来源是不同的。
 事件既可以同步使用,也可以异步使用 (取决于调用sendEvent()还是 postEvents()),而使用信号和槽总是同步的。事件的另一个好处是可以被过滤。
Qt中使用QEvent及其子类来描述事件(其继承关系见图2-7),比如QMouseEvent类用于描述与鼠标相关的事件,QKeyEvent描述了与键盘相关的事件等
根据事件的来源和传递方式,事件可分为以下三大类
 自发事件:这是由窗口系统生成的,这些事件置于系统队列中,并由事件循环一个接一个地处理。
 发布的事件(Posted events):该类事件由Qt或应用程序生成,这些事件由Qt排队,并由事件循环处理。
 发送的事件(Sent events):该类事件由Qt或应用程序生成,这些事件直接发送到目标对象,不经过事件循环处理。
事件还可被细分为很多种类型(有一百多种),每一种类型使用QEvent类中的枚举常量进行表示,比如QMouseEvent管理的鼠标事件有鼠标双击、移动、按下等类型,这些类型分别使用QEvent::Type枚举类型中的枚举常量MouseButtonDblClick、MouseMove、MouseButtonPress表示。所有的类型分类请查阅帮助文档。可使用函数 Type QEvent::type() const;获取事件的类型
使用Qt编程几乎不需考虑事件,因为当产生某种事件时,Qt窗口部件都会发射一个相应的信号(即Qt会把事件转换为一个对应的信号),比如按钮被按下时,会产生一个MouseButtonPress事件,Qt会处理这一事件,并且会发射一个clicked()单击信号,程序员可以直接处理clicked()信号,而不必处理底层的事件。
我们不需要知道Qt是怎样把事件转换为QEvent或其子类类型的对象的,程序员只需要知道怎样传递和处理这些事件即可。比如对于按下鼠标事件,不需要知道Qt是怎样把该事件转换为QMouseEvent类型的对象的,只需要知道怎样传递和处理该事件即可。
2.6.3 事件的传递(或分发)及处理

1、事件传递步骤(见图2-8):
基本规则:若事件未被目标对象处理,则把事件传递给其父对象处理,若父对象仍未处理,则再传递给父对象的父对象处理,重复这个过程,直至这个事件被处理或到达顶级对象为止。注意:事件是在对象间传递的,这里是指对象的父子关系,而不是指类的父子关系。
在Qt中有一个事件循环,该循环负责从可能产生事件的地方捕获各种事件,并把这些事件转换为带有事件信息的对象,然后由Qt的事件处理流程分发给需要处理事件的对象来处理事件。
通过调用QCoreApplication::exec()函数启动事件主循环。主循环从事件队列中获取事件,然后创建一个合适的QEvent对象或其子类类型的对象来表示该事件,在此步骤中,事件循环首先处理所有发布的事件,直到队列为空,然后处理自发的事件,最后处理在自发事件期间产生的已发布事件。注意:发送的事件不由事件循环处理,该类事件会被直接传递给对象。
然后,Qt会调用QCoreApplication::notify()函数对事件进行传递(或分发)
最后,QObject对象调用QObject::event()函数接收事件,
 event()函数是一个虚函数,是QObject对象处理事件的入口,
 在QObject的子类中通常会重写event()函数,比如QWidget类就重写了event()函数。
 event()函数与事件处理函数的关系:event()函数负责把事件传递给目标对象并调用对应的事件处理函数处理事件,比如调用QWidget::keyPressEvent()函数处理键盘按下事件等,注意,QObject中的event()函数并不能实现该功能,这是通过QObject的子类中重写的event()函数实现的,比如调用QWidget::keyPressEvent()函数,就是由在QWidget类中重写的event()函数来完成的。
 event()函数对大多数事件都调用了默认的处理函数,但并不能包括全部的事件,因此,有时我们需要重写该函数,这也是我们处理Qt事件的方式之一。
注意:event()函数不会处理事件,他只是根据事件的类型进行事件的传递(或分发),若返回true则表示这个事件被接受并进行了处理,否则事件未被处理,需进行进一步传递或丢弃。
在这里插入图片描述
2、事件的处理
任何QObject的子类都可以处理事件,QEvent及其子类虽然可以描述一个事件,但并不对事件进行处理。
事件是由类的成员函数(通常为虚函数)处理的,通常把处理事件的成员函数称为事件处理函数。由此可见,处理事件需要两个要求:处理该事件的对象和该对象中用于处理该事件的事件处理函数。
Qt为绝大多数常用类型的事件提供了默认的事件处理函数,并作了默认处理,这些事件处理函数位于QObject的子类中,比如QWidget::mousePressEvent()函数用于处理鼠标按下事件,QWidget::keyPressEvent()用于处理键盘按下事件等。对这些默认事件处理函数的调用是通过QObject::event()虚函数进行的,因此若默认的事件处理函数不能满足用户要求(比如对Tab键的处理),则可以通过重新实现event()函数来达到目的。下面为QWidget::event()的部分源代码(qwidget.cpp)

bool QWidget::event(QEvent *event){
……
switch (event->type()) {
……
//调用QWidget类的默认事件处理函数
case QEvent::MouseButtonPress:     mousePressEvent((QMouseEvent*)event);	 break;
case QEvent::MouseButtonRelease:   mouseReleaseEvent((QMouseEvent*)event);	break;
……		} 
…… }

3、由事件传递过程可见,Qt可以使用以下5种方式来处理事件
①、重新实现各部件内部默认的事件处理函数,比如重新实现paintEvent()、mousePressEvent()等事件处理函数,这是最常用、最简单的方式
②、重新实现QObject::event()函数(需继承QObject类),使用该方式,可以在事件到达默认的事件处理函数之前捕获到该事件。该方式常被用来处理Tab键的默认意义。在重新实现event()函数时,必须对未明确处理的事件调用基类的event()函数,比如,若子类化QWidget,并重写了event()函数,但未调用父类的event()函数,则程序可能会不能正常显示界面。
③、在QObject对象上安装(或称为注册)事件过滤器(见后文)。对象一旦使用installEventFilter()注册,传递给目标对象的所有事件都会先传递给这个监视对象的eventFilter()函数。或同一对象上安装了多个事件处理器,则按照安装的逆序依次激活这些事件处理器。
④、在QApplication上安装(或称为注册)事件过滤器。该方式与重新实现notify函数一样强大。一旦在QApplication对象(程序中只有一个这种类型的对象)上注册了事件过滤器,则程序中每个对象的每个事件都会在发送到其他事件过滤器之前,发送到这个eventFilter()函数。该方式对于调试非常有用。
⑤、子类化QApplication,并重新实现QCoreApplication::notify()函数,由事件传递过程可知,该函数提供了对事件的完全控制,因此功能非常强大,所以重写该函数会很复杂,也因此此方法很少被使用。这是唯一能在事件过滤器之前捕获到事件的方式。
注意:exec()、notify()和event()函数都不会处理事件,他们只是根据事件的类型进行事件的传递(或分发)。
示例2.17:事件处理的方式

#include <QApplication>
#include<QWidget>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:
      bool event(QEvent* e);  //事件处理方式1:重写虚函数QObject::event()
      void mousePressEvent(QMouseEvent* e); };//事件处理方式2:重写QWidget类中的事件处理函数
                      
bool A::event(QEvent* e){ static int i=0;   //i用于计数
    cout<<"E"<<i++<<endl;  //验证该函数能在事件到达目标对象之前被捕获
    if(e->type()==QEvent::KeyPress) //判断是否是键盘按下事件。
        cout<<"keyDwon"<<endl;
    return QWidget::event(e);  /*此处应调用父类的event函数以对未处理的事件进行处理,若此处不调用父类的event,则本例mousePressEvent处理函数中的内容将不会被执行。读者可使此处返回1或者0进行验证。*/
}
void A::mousePressEvent(QMouseEvent* e){//处理鼠标按下事件,这是Qt中最简单的事件处理方式。
cout<<"mouseDwon"<<endl;		}
int main(int argc, char *argv[]){
    QApplication a(argc,argv);  //在Qt中QApplication类型的对象只能有一个
    A ma;   //创建一个部件
    ma.resize(333,222);  //设置部件的大小
    ma.show();   //显示创建的部件

    a.exec();   //在此处进入事件主循环。
    return 0;    }

程序运行结果(见图2-9)及说明
在这里插入图片描述
只要窗口ma接收到事件就会输出Exx,可见event函数捕获事件的范围及时机,当按下键盘上的任一键时会输出"keyDown",当按下任一鼠标键时,会输出mouseDown

示例2.18:重写notify函数

#include <QApplication>
#include<QWidget>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:  bool event(QEvent* e); }; //重写虚函数QObject::event()
class AA:public QApplication{public:
 	 	AA(int i,char *p[]):QApplication(i,p){}
  		bool notify(QObject *o,QEvent *e);};  //重写虚函数notify

bool A::event(QEvent* e){    cout<<"E"<<endl;  //用于验证该函数是否被执行
   	 	return QWidget::event(e);		}   //应调用父类的event函数处理未处理的事件
bool AA::notify(QObject *o,QEvent *e){
    static int i=0;
    cout<<"N"<<i++<<endl;
    if(o->objectName()=="ma"&&e->type()==QEvent::KeyPress)  //若对象为ma且事件为键盘按下事件
        cout<<"keyDwon"<<endl;
    return QApplication::notify(o,e); } /*应调用父类的notify函数,否则本示例的event函数不会被执行,同时无法关闭窗口*/
int main(int argc, char *argv[]){
    AA aa(argc,argv);//在Qt中QApplication或其子类型的对象只能有一个
    A ma;   //创建一个部件
    ma.setObjectName("ma");  //设置对象名,方便在notify函数中调用
    ma.resize(333,222);  //设置部件的大小
    ma.show();   //显示创建的部件

    aa.exec();//在此处进入事件主循环。
    return 0;            }

程序运行结果(见图2-10)及说明
在这里插入图片描述
本示例可以看到notify()函数和event()函数的执行顺序,及notify函数和event函数捕获的事件的范围和时机,当用户按下键盘上的键时会输出keyDown

示例2.19:事件传递

#include <QApplication>
#include<QWidget>
#include<QPushButton>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:   bool event(QEvent* e);  };//子类化QWidget
class C:public QPushButton{public:    bool event(QEvent* e);}; //子类化标准按钮QPushButton

bool A::event(QEvent* e){
    if(e->type()==QEvent::KeyPress) //判断是否是键盘按下事件。
       {cout<<objectName().toStdString()<<"=keyDwon"<<endl; }
    if(e->type()==QEvent::MouseButtonPress) //判断是否是鼠标按下事件。
       {cout<<objectName().toStdString()<<"=mouseDwon"<<endl; }
    if(e->type()==QEvent::MouseButtonRelease) //判断是否是鼠标释放事件。
       {cout<<objectName().toStdString()<<"=mouseRelease"<<endl;  }
return QWidget::event(e);	}

bool C::event(QEvent* e){
    if(e->type()==QEvent::KeyPress) //判断是否是键盘按下事件。
       {cout<<objectName().toStdString()<<"=keyDwon"<<endl;
        	return 0;  }//将事件传递给父对象处理
    if(e->type()==QEvent::MouseButtonPress) //判断是否是鼠标按下事件。
       {cout<<objectName().toStdString()<<"=mouseDwon"<<endl;
        return 1; }  //事件不传递
    return QWidget::event(e);}

int main(int argc, char *argv[]){
    QApplication a(argc,argv);  //在Qt中QApplication类型的对象只能有一个
    A ma;   C *mc=new C();
    mc->setParent(&ma);  /*设置父对象为ma,若mc未处理的对象会传递给此处设置的父对象ma处理,注意事件传递是在对象之间的父子关系,而不是类之间的父子关系。*/
    mc->setText("AAA");
    mc->move(22,22);  //设置mc相对于ma的位置
    ma.setObjectName("ma");   //设置对象名
    mc->setObjectName("mc");
    ma.resize(333,222);  //设置部件的大小
    ma.show();
    a.exec();   //在此处进入事件主循环。
    return 0;     }

程序运行结果(见图2.11)及说明
在这里插入图片描述
当在按钮上按下鼠标键时,调用C::event()函数输
出mc=mouseDown,此时该函数返回1,表示事件不
再传递。但是该函数并未处理鼠标释放事件,因此调用QWidget::event()对该事件作默认处理,因默认未处理该事件,因此调用mc的父对象ma处理该事件,此时调用A::event()函数输出ma=mouseRelease,至此鼠标事件处理结束。
当按钮获得焦点,按下键盘上的按键时的处理方式与鼠标事件类似,只是C::event()函数直接把键按下事件传递给了父对象ma处理。

2.6.4 事件的接受和忽略

事件可以被接受或忽略,被接受的事件不会再传递给其他对象,被忽略的事件会被传递给其他对象处理,或者该事件被丢弃(即没有对象处理该事件)。
使用QEvent::accept()函数表示接受一个事件,使用QEvent::ignore()函数表示忽略一个事件。也就是说若调用accept(),则事件不会传递给父对象,若调用ignore()则事件会向父对象传递。
Qt默认值是accept (接受事件),但在QWidget的默认事件处理函数(比如keyPressEvent())中,默认值是ignore(),因为这样可实现事件的传递(即子对象未处理就传递给父对象处理)。因此对事件的接受和忽略,最好是明确的调用accept()和ignore函数。
在event()函数中调用accept()或ignore()是没有意义的,event()函数通过返回一个bool值来告诉调用者是否接受了事件(true表示接受事件)。是否调用accept()是用于事件处理函数与event()函数之间通信的,而event()函数返回的bool值是用于与QApplication::notify()函数之间通信的。
注意:QCloseEvent(关闭事件)有一些不同,QCloseEvent::ignore()表示取消关闭操作,而QCloseEvent::accept()则表示让Qt继续关闭操作。为避免产生混淆,最好在closeEvent()处理函数的重新实现中显示地调用accept()和ignore()。
示例2.20:事件的接受和忽略

#include <QApplication>
#include<QWidget>
#include<QKeyEvent>
#include<QMouseEvent>
#include<QPushButton>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:  
		//重写以下事件处理函数
void mousePressEvent(QMouseEvent *e){  cout<<"AmouseDwon"<<endl;}  //鼠标按下
        void mouseReleaseEvent(QMouseEvent *e){  cout<<"AmouseRelease"<<endl;} //鼠标释放
        void keyPressEvent(QKeyEvent* e){   cout<<"AkeyDwon"<<endl;}        //键盘按下
        void keyReleaseEvent(QKeyEvent* e){    cout<<"AkeyRelease"<<endl;}  };   //键盘释放
class C:public QPushButton{public:    
void mousePressEvent(QMouseEvent *e){    cout<<"CmouseDwon"<<endl;
    					e->accept();	}   //验证事件被接受后不会再被传递给父对象
         void mouseReleaseEvent(QMouseEvent *e){		cout<<"CmouseRelease"<<endl;
    					QWidget::mouseReleaseEvent(e);  }//验证QWidget默认为忽略事件
         void keyPressEvent(QKeyEvent* e){	cout<<"CkeyDwon"<<endl;
    					e->ignore();   } //验证事件事件被忽略后会被传递给父对象
         void keyReleaseEvent(QKeyEvent* e){      cout<<"CkeyRelease"<<endl;
   			  //验证Qt的默认处理方式为接受事件(此处未明确调用accept()或ignore()函数
}         }; 
int main(int argc, char *argv[]){
    QApplication a(argc,argv);  //在Qt中QApplication类型的对象只能有一个
    A ma;   C *mc=new C();
    mc->setParent(&ma);  //设置父对象为ma
    mc->setText("AAA");
    mc->move(22,22);  //设置mc相对于ma的位置
    ma.resize(333,222);  //设置部件的大小
    ma.show();
    a.exec();   //在此处进入事件主循环。
    return 0;     }

程序运行结果(见图2-12)及说明
在这里插入图片描述
当在按钮上按下鼠标时,调用C::mousePressEvent()函数,该函数接受该事件,因此事件不再传递。因此仅输出CmouseDown。
当释放鼠标时调用C::mouseReleaseEvent()函数输出CmouseRelease,该函数调用QWidget的默认事件处理函数忽略该事件,因此事件传递给mc的父对象ma处理,此时调用A::mouseReleaseEvent()函数输出AmouseRelease。
④~⑥的键盘事件与鼠标事件类似,只是⑥表示的是Qt的默认处理方式为接受该事件
示例2.21:event()函数与事件处理函数的关系。

#include <QApplication>
#include<QWidget>
#include<QMouseEvent>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:
    bool event(QEvent* e){
        if(e->type()==QEvent::MouseButtonPress)   //处理鼠标事件
             {cout<<"AE"<<endl;
            mousePressEvent((QMouseEvent*)e);  /*明确调用鼠标事件处理函数处理该事件,需要注意的是Qt对事件处理函数的默认处理方式是接受事件。*/
            //return QWidget::event(e); /*也可通过父类的event函数间接的调用鼠标事件处理函数。
            return 0; }      //event()函数的返回值与事件处理函数没有关系,返回0只表示该事件可以传递给父对象处理,返回1则事件不再传递。*/
        return QWidget::event(e);  //其他事件使用父类的event函数处事
        }
    void mousePressEvent(QMouseEvent *e){cout<<"AK"<<endl;} };
int main(int argc, char *argv[]){
QApplication aa(argc,argv);   
 A ma;     ma.resize(333,222);     ma.show();      aa.exec();    return 0;     }

运行结果说明:在窗口中点击鼠标会先后输出AE和AK
本文作者:黄邦勇帅(原名:黄勇)

猜你喜欢

转载自blog.csdn.net/g200407331/article/details/89577309