• QT Embedded二三事之QObject的元对象


    一、元对象 

           元对象被称做是meta object.在运行时刻(runtime),能够提供对象的运行时信息。

           在C++语言发展的早期,C++语言本身没有定义对象的运行时信息,如输出类的名称,父类的名称,判断对象的继承关系等等。虽然新的C++语言增加了RTTI,但还是不够的。这些信息在面向对象编程和调试程序过程中是非常有用的。因此不同的类库采取了不同的方式来扩展语言。
             MFC的实现方式是宏定义:如
              DECLARE_DYNAMIC,DECLARE_DYNCREATE,DECLARE_SERIAL。这种实现方式没有扩展语言本身,因此可以不加处理的兼容所有的C++编译器。
           QT的实现方式是宏定义加moc编译,定义宏Q_OBJECT,并对语言本身做了部分扩展,因此需要用QT的moc编译器特殊处理一下,产生一个moc_XXX.cpp的文件,然后就可以使用通用C++编译器编译了。
     Q_OBJECT的定义如下:
    Cpp代码  收藏代码
    1. #define Q_OBJECT   
    2. public:   
    3.   virtual QMetaObject *metaObject() const {   
    4.   return staticMetaObject();   
    5.   }   
    6.   virtual const char *className() const;   
    7.   virtual void* qt_cast( const char* );   
    8.   virtual bool qt_invoke( int, QUObject* );   
    9.   virtual bool qt_emit( int, QUObject* );   
    10.   QT_PROP_FUNCTIONS   
    11.   static QMetaObject* staticMetaObject();   
    12.   QObject* qObject() { return (QObject*)this; }   
    13.   QT_TR_FUNCTIONS   
    14. private:   
    15.   static QMetaObject *metaObj;  
     
     
           从定义上看,QT的元对象信息主是通过QMetaObject对象来管理的,每一个类都会增加一个static QMetaObject *metaobj。QMetaObject中包含三部分信息:
     (1)className,superclassname
            这是用来判断对象的继承关系的,是实现QObject::isA(const char          *classname)和QObject::inherits(const char *classname)的基础。
     (2)用来实现Q_PROPERTY的property信息。
     (3)用来实现signal/slot的信息

    二、 signal/slot 

           signal/slot机制是QT最具特色的特性。signal/slot巧妙的简单的实现了面向对象编程中经常使用的观察者模式(observer,或称为消息预定模式)。同时也封装了callback机制,一定程度上保证了callback函数的类型安全。

          从实现上来看,signal/slot需要QMetaObject和moc编译器的支持。signal和slot实际上是两种类函数,分别需要在类函数声明前面加signals和slots两个宏。
     以QButton的一个signals和slots为例说明实现过程:
    Cpp代码  收藏代码
    1. class Q_EXPORT QButton : public QWidget  
    2. {  
    3.   Q_OBJECT  
    4.  .........   
    5. signals:  
    6.   void pressed();  
    7.  ................  
    8. public slots:  
    9.   void animateClick();  
    10. };  
     

     (1)signal/slot机制需要增加Q_OBJECT声明,signal/slot是元对象信息,需要QMetaObject的支持。

     (2)signals、slots、emit都是宏,从定义上看,都是空值。对C++编译器编译没有任何影响。实际上,这三个宏都是给QT的moc编译器使用的。moc编译器会对标记为signals和slots的函数编号,方便的实现(编号,函数名字,函数地址)三者之间的转换。同时实现了标记为signals的函数。因此pressed()函数的是由moc编译器实现的,不需要用户实现,具体内容在moc_qbutton.cpp中。

     (3)connect/disconnect
     这两个函数的定义如下:
            QObject::connect( const QObject *sender, const char *signal, const QObject *receiver, const char *member )
            QObject::disconnect( const QObject *sender, const char *signal, const QObject *receiver, const char *member )

            QMetaObject中会为每一个signal建立一个QConnectList列表,记录着连接到这个signal的所有receiver和其slot函数。 connect就是在QConnectList中增加一项,让sender的QMetaObject记住。disconnect的作用相反。

     (4)emit pressed()

            emit是一个宏,但是一个空值,因此emit pressed()==pressed()。pressed()是由moc编译器实现的。 QButton::pressed()就是把通过QConnectList把所连接的receiver和其member函数找出来,然后按先后顺序调用receiver->member(),由于是一个list,所以是先连接的slot先执行。这与Observer模式中的实现是一致的。

             从上面的分析来看,signal/slot是同步直接调用。实现有些复杂,效率上比callback函数也慢一些,但是对于面向对象编程来说,非常符合对象之间低耦合的思想,减少了对象之间的依赖。使用起来也灵活。函数接口也可以任意组合。的确是QT的特色。

             使用过程中,也需要注意如下几点:
            (1)QT的signal和unix操作系统的signal是不同的,unix的signal是进程之间的一种通讯方式,QT的signal是对象之间的一种消息传递方式,不是一个层面的概念。
            (2)slot函数的返回值是没有意义的,signal函数是由moc编译器实现的,从具体实现来看,不关心slot函数的返回值。
            (3)根据目前的实现,slot函数的被执行是有顺序的,但是这要依赖于目前的实现,以后或许会变
            (4)signal/slot和(public,protected,private)没有冲突,private的函数也可以是signal/slot
            (4)slot函数可以是虚函数吗?
            (5)signal函数可以是虚函数吗?

    三、 event机制
            在面象对象编程中,对象是核心。对象之间的需要通讯,对象A需要给对象B发消息。A对象向怎么才能向B对象发消息message? 一般需要把message定义成B对象的一个函数,然后由A对象调用b->message.这种实现不灵活,每个message都要定义函数。
           在UI系统中,不同的widget会经常发各种消息,要用上面的方式肯定是不行的。通常UI系统都会把这些消息和消息参数规范化。QT把消息类型和消息参数都封装到QEvent及其子类中,同时定义了QObject::event(QEvent *e)函数来处理各种QEvent,并且在此基础上实现了消息钩子和消息拦截等功能。这就是QT的event机制,下面详细介绍一下具体实现。

      1.QEvent
             QEvent封装了消息类型和消息参数,还有一些属性。下面是其实现代码:

    Cpp代码  收藏代码
    1. class Q_EXPORT QEvent: public Qt // event base class  
    2. {  
    3. public:  
    4.   enum Type {  
    5.   
    6.  None = 0, // invalid event  
    7.  Timer = 1, // timer event  
    8.  MouseButtonPress = 2, // mouse button pressed  
    9.  MouseButtonRelease = 3, // mouse button released  
    10.  MouseButtonDblClick = 4, // mouse button double click  
    11.  MouseMove = 5, // mouse move  
    12.  KeyPress = 6, // key pressed  
    13.  KeyRelease = 7, // key released  
    14.  FocusIn = 8, // keyboard focus received  
    15.  FocusOut = 9, // keyboard focus lost  
    16.  Enter = 10, // mouse enters widget  
    17.  Leave = 11, // mouse leaves widget  
    18.  ..................  
    19.  WindowStateChange = 96, // window state has changed  
    20.  IconDrag = 97, // proxy icon dragged  
    21.  User = 1000, // first user event id  
    22.  MaxUser = 65535 // last user event id  
    23.   };  
    24.   QEvent( Type type ) : t(type), posted(FALSE), spont(FALSE) {}  
    25.   virtual ~QEvent();  
    26.   Type type() const { return t; }  
    27.   bool spontaneous() const { return spont; }  
    28. protected:  
    29.   Type t;  
    30. private:  
    31.   uint posted : 1;  
    32.   uint spont : 1;  
    33.   friend class QApplication;  
    34.   friend class QAccelManager;  
    35.   friend class QBaseApplication;  
    36.   friend class QETWidget;  
    37. };  
     
     QEvent首先将各种消息编号,如    
           KeyPress,KeyRelease,FocusIn,FocusOut,Show,并用变量t标识类型,同时还有两个属性:posted用来标识QEvent是由sendEvent发出还是由postEvent发出的;spont的含义不明,从代码上看,只有通过sendSpontaneousEvent发出的QEvent的spont才是true;
     
     2.QObject::event(QEvent *e)
            QObject::event是QT的消息处理函数。下面分析其实现:
    Cpp代码  收藏代码
    1. bool QObject::event( QEvent *e )  
    2. {  
    3. #if defined(QT_CHECK_NULL)  
    4.   if ( e == 0 )  
    5.  qWarning( "QObject::event: Null events are not permitted" );  
    6. #endif  
    7.  //这里的消息钩子和消息拦截  
    8.   if ( eventFilters ) { // try filters  
    9.  if ( activate_filters(e) ) // stopped by a filter  
    10.   return TRUE;  
    11.   }  
    12.   
    13.  //下面就是分发消息了  
    14.   switch ( e->type() ) {  
    15.   case QEvent::Timer:  
    16.  timerEvent( (QTimerEvent*)e );  
    17.  return TRUE;  
    18.   
    19.   case QEvent::ChildInserted:  
    20.   case QEvent::ChildRemoved:  
    21.  childEvent( (QChildEvent*)e );  
    22.  return TRUE;  
    23.   
    24.   case QEvent::DeferredDelete:  
    25.  delete this;  
    26.  return TRUE;  
    27.   
    28.   default:  
    29.  if ( e->type() >= QEvent::User ) {  
    30.   customEvent( (QCustomEvent*) e );  
    31.   return TRUE;  
    32.  }  
    33.  break;  
    34.   }  
    35.   return FALSE;  
    36. }  
     
           从实现上看,QObject::event函数相当于所有消息的一个分发函数,根据e->type的类型再分别调用,其相应的函数,如timerEvent,keyPressEvent等。

      3.消息钩子和消息拦截
            UI系统中,经常需要知道是否某一个widget收到某一类型的消息,例如没有focus的widget想接收当前keypress或keyrelease事件,这就需要在当前有foucs的widget上面安装一个钩子hook,并且在适当的时候截断这个消息,使得即使有focus的widget也不能收到事件。这就是消息钩子和消息拦截功能。在QT中,这是通过eventFilter来实现的。
     首先,需要调用QObject::installEventFilter安装eventFilter,即focuswidget->installEventFilter(nonfocuswidget)。
           下面,我们看看当有QEvent到来时发生了什么。根据QObject::event的实现,QObject::event会首先检查是否有eventFilter,如果有就会执行activate_filters,下面是activate_filters的实现。

    Cpp代码  收藏代码
    1. bool QObject::activate_filters( QEvent *e )  
    2. {  
    3.   if ( !eventFilters ) // no event filter  
    4.  return FALSE;  
    5.   QObjectListIt it( *eventFilters );  
    6.   register QObject *obj = it.current();  
    7.   while ( obj ) { // send to all filters  
    8.  ++it; // until one returns TRUE  
    9.  if ( obj->eventFilter(this,e) ) {  
    10.   return TRUE;  
    11.  }  
    12.  obj = it.current();  
    13.   }  
    14.   return FALSE; // don't do anything with it  
    15. }  
    16.      
     
            activate_filters会依次执行每个安装的QObject::eventFilter,这是一个虚函数。这样用户可以改eventFilter,加上自已的逻辑实现,这样nonfocuswidget就知道目前发生了一个keypress事件。
           从代码中,还可以看出,如果有一个eventFilter函数返回值是TRUE,将会停止执行eventFilter函数,并返加TRUE到QObject::event,然后QObject::event也不会继续执行,这样就使实现了消息拦截功能。

      4.QEvent与Win32的消息对比
           Win32中,也对所有的消息进行了封装,也有消息处理函数,不过Win32的实现与QT的实现差别还是很大的:
           (1)Win32提供的是C接口,所以没有把消息封装成类,这个即使是MFC也没有。Win32中,消息类型是一个UINT,消息参数固定成两个参数(WPARAM wParam, LPARAM lParam)。
          (2)Win32的消息处理函数WindowProc也是一个C接口的函数,它的原型如下:
           LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
           其中uMsg是消息类型,wParam和lParam是两个消息参数。从这点来看,Win32的实现简洁一些。
           (3)Win32中的消息是围绕window定义,window是处理消息的主体。在QT中,消息处理定义到了QObject这一层,而不是在QWidget上,因此所有的       QObject都可以处理消息。QT中的消息也不仅局限于窗口消息,如ChildInserted和ChildRemoved就是处理两个QObject之间父子关系的。
          (4)Win32中的消息钩子功能要比eventFilter强大,不公能拦截本进程内窗口的消息,还能拦截整个系统内所有窗口的消息。


     5.event和signal/slot的对比
            event和signal/slot都是QT提供的两种对象间通讯的方式,下面是一些不同点:
           (1)signal/slot的需要元对象模型的支持,需要声明Q_OBJECT。event与元对象模型无关。
           (2)emit signal是一种同步直接调用,对于event来说,sendEvent是同步直接调用,postEvent是异步调用。
           (3)用户可以自定义signal/slot的类型,event的类型都是提前定义好的,从这一点上看,signal/slot更为灵活。
           (4)signal/slot缺少消息钩子和消息拦截功能

           QApplication代表着QT Embedded应用程序的运行时对象,类似于MFC的CWinApp。应用程序可以通过一个全局指针变量qApp表访问当前应用程序的QApplication。 对于QT应用来说,第一步就是要创建一个QApplication对象。         在此过程中,做了如下事情:
     (1)初始化qApp
     (2)对于QWS Server,创建QWSServer全局指针qwsServer
     (3)创建QDisplay全局指针qt_fbdpy
     (4)创建QScreen全局指针qt_screen

    1.初始化qApp

    2.创建QWSServer全局指针qwsServer

    3.创建QDisplay全局指针qt_fbdpy

    4.创建QScreen全局指针qt_screen

     
  • 相关阅读:
    报错:maven 程序包org.junit不存在
    Sublime Text 3
    Win10使用技巧
    CookieHelper
    log4net
    文件导出 Aspose
    C# 数字证书加载 X509Certificate2
    前后端文件上传
    WebApi 后端文件传输至远程服务器
    aspnet_regiis.exe用法(备忘非自创)
  • 原文地址:https://www.cnblogs.com/lvdongjie/p/3757532.html
Copyright © 2020-2023  润新知