一、元对象 

元对象被称做是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的定义如下:

  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为例说明实现过程:

  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封装了消息类型和消息参数,还有一些属性。下面是其实现代码:

  1. class Q_EXPORT QEvent: public Qt // event base class
  2. {
  3. public:
  4. enum Type {
  5. None = 0, // invalid event
  6. Timer = 1, // timer event
  7. MouseButtonPress = 2, // mouse button pressed
  8. MouseButtonRelease = 3, // mouse button released
  9. MouseButtonDblClick = 4, // mouse button double click
  10. MouseMove = 5, // mouse move
  11. KeyPress = 6, // key pressed
  12. KeyRelease = 7, // key released
  13. FocusIn = 8, // keyboard focus received
  14. FocusOut = 9, // keyboard focus lost
  15. Enter = 10, // mouse enters widget
  16. Leave = 11, // mouse leaves widget
  17. ..................
  18. WindowStateChange = 96, // window state has changed
  19. IconDrag = 97, // proxy icon dragged
  20. User = 1000, // first user event id
  21. MaxUser = 65535 // last user event id
  22. };
  23. QEvent( Type type ) : t(type), posted(FALSE), spont(FALSE) {}
  24. virtual ~QEvent();
  25. Type type() const { return t; }
  26. bool spontaneous() const { return spont; }
  27. protected:
  28. Type t;
  29. private:
  30. uint posted : 1;
  31. uint spont : 1;
  32. friend class QApplication;
  33. friend class QAccelManager;
  34. friend class QBaseApplication;
  35. friend class QETWidget;
  36. };

QEvent首先将各种消息编号,如    
       KeyPress,KeyRelease,FocusIn,FocusOut,Show,并用变量t标识类型,同时还有两个属性:posted用来标识QEvent是由sendEvent发出还是由postEvent发出的;spont的含义不明,从代码上看,只有通过sendSpontaneousEvent发出的QEvent的spont才是true;
 
 2.QObject::event(QEvent *e)
        QObject::event是QT的消息处理函数。下面分析其实现:

  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. switch ( e->type() ) {
  14. case QEvent::Timer:
  15. timerEvent( (QTimerEvent*)e );
  16. return TRUE;
  17. case QEvent::ChildInserted:
  18. case QEvent::ChildRemoved:
  19. childEvent( (QChildEvent*)e );
  20. return TRUE;
  21. case QEvent::DeferredDelete:
  22. delete this;
  23. return TRUE;
  24. default:
  25. if ( e->type() >= QEvent::User ) {
  26. customEvent( (QCustomEvent*) e );
  27. return TRUE;
  28. }
  29. break;
  30. }
  31. return FALSE;
  32. }

从实现上看,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的实现。

  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. }

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

 

最新文章

  1. 【翻译svg教程 】svg 的坐标系统
  2. iOS在线更新framework,使用NSBundle动态读取
  3. WampServer下如何实现多域名配置(虚拟域名配置)
  4. cpoint
  5. 13个Cat命令管理(显示,排序,建立)文件实例
  6. Redshift扩容及踩到的坑
  7. 【Qt编程】基于Qt的词典开发系列--后序
  8. Creating Excel files with Python and XlsxWriter(通过 Python和XlsxWriter来创建Excel文件(xlsx格式))
  9. UVA1660 电视网络 Cable TV Network
  10. About me & OI这一年
  11. 刘志梅2017710101152.《面向对象程序设计(java)》第一周学习总结
  12. java:给图片上加水印
  13. [AWS] SSO: Single sign-on
  14. 常用的Array相关的属性和方法
  15. Platinum UPnP
  16. (工具类)MD5算法|时间格式转换|字符串转数字
  17. 【20181030T1】排列树【树形结构+组合数】
  18. Android-ListView-ArrayAdapter
  19. Part 1: Setting up ARM GNU tool chain
  20. 安装sqoop 1.99.4

热门文章

  1. react portals 插槽 实现简易弹窗
  2. requests模块发送数据
  3. angularJs同步请求
  4. Oracle cmd 命令
  5. 【Struts2】进阶
  6. django框架—终端命令
  7. PXE批量部署安装Linux系统
  8. Django:中间件与csrf
  9. Hadoop_33_Hadoop HA的搭建
  10. Django 语法笔记