QtAssitant(Qt5.2.1)中与Qt的元对象系统和事件机制相关的几个重要段落或函数说明



目录

译注:本篇博文主要翻译了QtAssitant中与Qt的元对象系统和事件机制相关的几个重要段落或函数说明,还有一小部分我自己的补充。
Qt类
    QtObejctModel:Qt对象模型
    T qobject_cast(QObject * object) ,QObject的动态类型映射
QEventLoop类中与Qt的事件机制相关的几个重要函数
    int QEventLoop::exec(ProcessEventsFlags flags = AllEvents)
    bool QEventLoop::processEvents(ProcessEventsFlags flags = AllEvents)
QCoreApplication类中与Qt的事件机制相关的几个重要函数
    bool QCoreApplication::notify(QObject * receiver, QEvent * event) [virtual]
    bool QCoreApplication::sendEvent(QObject * receiver, QEvent * event) [static]
    void QCoreApplication::postEvent(QObject * receiver, QEvent * event, int priority = Qt::NormalEventPriority) [static]
    void QCoreApplication::sendPostedEvents(QObject * receiver = 0, int event_type = 0) [static]
QObject类中与Qt的事件机制相关的几个重要函数
    bool QObject::event(QEvent * e) [virtual]
    void QObject::timerEvent(QTimerEvent * event) [virtual protected]
    void QObject::installEventFilter(QObject * filterObj)
    bool QObject::eventFilter(QObject * watched, QEvent * event) [virtual]
附加内容


Qt

Qt类本身并算不上一个类,它只是一个命名空间。它包含了Qt库中所用到的各种各样的通用标识的定义,Qt namespace contains miscellaneous identifiers used throughout the Qt library.。


QtObejctModel:Qt对象模型


与标准C++对象模型相比,Qt新增的特性:
提供了一套强大的实现对象间无缝通讯的机制:信号(signal)和槽(slot);
可查询、可设计的对象特性;
强大的事件模型(event)和事件过滤器(event filter);
上下文字符串翻译,便于国际化;
拥有精细的毫秒间隔的定时器,便于在事件驱动型的GUI中完美地集成多项任务;
分层的、可查询的树形对象结构,使对象之间有很清晰的所属继承关系;
安全指针(QPointer),所指对象被销毁时会自动清零指向NULL;
数据类型的动态映射(dynamic cast);
支持用户自定义新的QT数据类型。
QT的大部分特性都是用C++实现的(通过继承QObject),但QT的信号-槽机制和动态属性需要元对象系统(Meta-Object-System)实现,这部分则需要用QT自己专门的工具(moc,Meta-Object-Compiler)来编译。QMetaObject能提供Qt Objects的元信息。

几个重要的类


QMetaObject     包含了Qt objects的元信息
QMetaMethod 成员函数的元数据
QMetaEnum       枚举类型的元数据
QMetaProperty   成员变量的元数据
QMetaClassInfo  类的附加信息
QMetaType       管理一个meta-object系统中已经命名的数据类型
QObject         所有Qt objects 的基类
QObjectCleanupHandler   监视多个的QObject的生命周期
QPointer            QObject安全指针的模板类
QSignalMapper   绑定可识别的发送者传来的signal
QVariant            可以看成由几乎所有Qt数据类型组成的联合体


QMetaObject 类


QMetaObject 包含了与Qt Object相关的元信息(包括类名、方法数量、方法名等等,总之通过这个类几乎可以查询到任何与某个QObject子类相关的信息)。
Qt里的元信息系统主要负责对象间的信号槽通信机制、运行时刻的类型信息、Qt属性系统。Qt会为应用程序中用到的每一个QObject子类创建一个单独的QMetaObject实例,(“单独的”是说,不管该子类有多少个实例,他们都共用相同的QMetaObject实例,这是因为QMetaObject描述的是一个类范有的信息,这些信息适用于它所有的实例),这个QMetaObject实例描述了与该子类相关的所有信息。QObject子类的对象(实例)可以通过QObject::metaObject来获得指向该子类对应的QMetaObject实例的指针。
对于通常的应用程序,在编程时不是很常用到QMetaObject这个类,但是当开发一些元应用时,比如脚本引擎、GUI创建工具等,这个类就很有用了。
QMetaObject较为常用的几个特性如下
   className() 返回类的名称
   superClass() 返回父类对应的QMetaObject实例
   method()  &  methodCount() 提供与该类的方法(成员函数)相关的信息。
   enumerator()& enumeratorCount()提供与该类的枚举相关的信息
   propertyCount()& property()提供与该类的属性(成员变量)相关的信息
   constructor()& constructorCount()提供与该类的构造函数相关的信息
对于QMetaMethod、QMetaEnum等类,都是QMetaObject在描述类的信息中用到的数据类型。对于它们,暂时了解这一点就够了。


T qobject_cast(QObject * object)


如果object是T类(或其子类)实例的指针,则返回将object映射为T类后的对象模型的指针;否则,返回NULL,如果object本身是NULL,自然也会返回NULL。
T类必须是从QObjec类直接或间接继承而来,并且在声明T类的时候要使用Q_OBJECT宏。(每个类都被视为其自身的一个子类)。

QObject *obj = new QTimer;          // QTimer inherits QObject
QTimer *timer = qobject_cast<QTimer *>(obj);
// timer == (QObject *)obj
QAbstractButton *button = qobject_cast<QAbstractButton *>(obj);
// button == 0



QEventLoop类中与Qt的事件机制相关的几个重要函数


int QEventLoop::exec(ProcessEventsFlags flags = AllEvents)


进入主事件循环,直到exit()被调用时再返回。返回值就是调用exit时传递给exit的参数(exit函数的简介在下面)。
如果参数flags被指定了,则只有特定类型的事件才会被处理。
如果要使事件处理函数生效,这个函数是必须调用的。主事件循环会从窗口系统接收事件并把这些事件派发给应用程序的窗口部件(由它们来执行对应的事件处理函数)。
一般来说,在调用exec()之前,应用程序中各部分的通讯和动作等都不会发生。不过特殊地是,模态的窗口部件(比如QMessageBox消息提示框)却可以在调用exec()之前使用,这时因为模态的窗口部件有他们自己的局部事件循环。
为了使你的应用程序能执行空闲处理,(就是说,每当已经没有事件处于待命状态时都执行一段特殊的程序),可以使用一个时间间隔为0的QTimer。更多高级的空闲处理方法能通过使用processEvents()来实现。


附:void QEventLoop::exit(int returnCode = 0)
使事件循环退出,并返回returnCode。
这个函数调用后,事件循环会返回到调用exec()的地方,exec()的返回值是returnCode。
为了方便,返回值为0表示成功,非零则表示有错误。
注意,这个exit不像标准C库中同名的exit函数,这个exit会返回到调用它的地方,而C库中的exit是直接使程序退出。


bool QEventLoop::processEvents(ProcessEventsFlags flags = AllEvents)


处理与flags指定的相符的待命的事件知道已经没有待命的事件。如果有待命的事件被处理了就返回true,否则返回false.
当在执行一段很耗时的操作而又想在不允许用户输入的情况下显示该操作的进度条时,这个函数就很有用了,可以通过设置flag为ExcludeUserInputEvents来实现此目的。
这个函数只是对QAbstractEventDispatcher::processEvents()的简单封装,更多细节看下面对QAbstractEventDispatcher::processEvents()的介绍。


附:bool QAbstractEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) [纯虚函数]
处理与flags指定的相符的待命的事件知道已经没有待命的事件。如果有待命的事件被处理了就返回true,否则返回false.
当在执行一段很耗时的操作而又想在不允许用户输入的情况下显示该操作的进度条时,这个函数就很有用了,可以通过设置flag为ExcludeUserInputEvents来实现此目的。
如果flags中设置了 QEventLoop::WaitForMoreEvents 标志,函数会这样操作:
如果有可用的事件,则马上处理它们,然后返回;
如果暂时没有可用的事件,会等待直到有可用事件到来,然后在处理这些新到来的事件,之后返回;
如果flags中没有设置QEventLoop::WaitForMoreEvents 标志,那么当没有可用事件时会立即返回。
注意:这个函数不会持续处理事件(那是exec()的工作),当所有可用事件被处理完时就会返回。


QCoreApplication类中与Qt的事件机制相关的几个重要函数


bool QCoreApplication::notify(QObject * receiver, QEvent * event) [virtual]


向receiver发送事件,由receiver的event函数(它是receiver这个对象的事件handler):receiver->event(event)。返回值是从receiver的事件handler返回的值。注意这个函数可以在任何线程中发送给任何对象任意的信号。(译注:看来有必要看下Qt中notify的源码,主要看看在事件跨线程发送时系统会怎么运作,会马上切换到目标线程去执行eventHandler吗?不然怎么得到返回值)。
对于特定类型的events(比如鼠标和键盘事件),如果receiver对该事件不感兴趣(event返回false),事件就会从receiver对象开始,逐层向上(找parent)传递,直到最高层的parent对象。
有五种不同的途径可以对事件进行处理;重新实现本函数(虚)只是其中一种。所有五种途径都在下面列出:
重新实现paintEvent(), mousePressEvent()等等这些虚函数,这是最普遍的方法,最简单不过优先级最低,处理的事件单一,功能很弱;
重新实现本函数(notify),这种方式有很强大的功能,能够提供完全的事件控制;但是一次只有一个子类的notify真正生效(译注:C++的多态性;一个应用程序只能有一个QCoreApplication或其子类QGuiApplication、QApplication的实例);
在QCoreApplication::instance()上安装事件过滤器。(instance()返回的是当前应用程序application的指针,本方法是在应用程序上安装事件过滤器,下面还有在对象上安装事件过滤器的方法)。这样的事件过滤器可以处理所有的窗口事件,所以他几乎和重新实现notify有着同样强大的功能;此外,全局事件过滤器可以有不止一个。全局事件过滤器甚至可以处理被disable掉的窗口的鼠标事件。注意应用程序的事件过滤器只能被生存于主线程中的对象调用;
重新实现QObject::event()(QWidget就是这么做的)。如果这样做了,你就能捕捉到Tab键按下的事件(译注:Tab键一般用来切换焦点窗口,所以地位略高),而且可以在事件进入事件过滤器之前就处理他们;
在对象上安装事件过滤器。这样的事件过滤器会得到包括Tab和Shift+Tab键在内的所有事件,只要他们捕捉到这两个事件后不切换焦点窗口(译注:这句翻译的不太准确?Installing an event filter on the object. Such an event filter gets all the events, including Tab and Shift+Tab key press events, as long as they do not change the focus widget.)。


bool QCoreApplication::sendEvent(QObject * receiver, QEvent * event) [static]


将事件event直接发送给接收者receiver,内部使用了notify()函数。返回值是event的handler的返回值。
注意,这个函数参数中的event在事件发送后不会被delete。通常的途径是在栈中创建event对象,像这样:

QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
QApplication::sendEvent(mainWindow, &event);


void QCoreApplication::postEvent(QObject * receiver, QEvent * event, int priority = Qt::NormalEventPriority) [static]


将以receiver为接收者的事件event投递到事件队列中,并立即返回。
事件event必须是在堆中分配的,因为事件队列会占用event并且在它被投递后将他delete掉(译注:处理完后??))。当一个event被本函数投递后再去访问它是不安全的,要避免这样的操作。
当控制权回到主事件循环中时,所有存储在队列中的事件都会被notify函数发送。
队列中的事件会按优先级递降的顺序排序,就是说事件优先级高的会排在优先级低的前面。优先级可以是任一个整型值,即在INT_MAX 和 INT_MIN之间。更多细节参见Qt::EventPriority。优先级相同的事件会按照投递的顺序排列。
注意: 这个函数是线程安全的


void QCoreApplication::sendPostedEvents(QObject * receiver = 0, int event_type = 0) [static]


立即将之前被QCoreApplication::postEvent()投递到队列中的所有事件类型为event_type且接收者为receiver的事件派发出去。
注意,窗口系统传来的事件不会被这个函数派发,那些事件还是要由processEvents()来做。
另外如果接受者receiver是NULL,那么所有事件类型为event_type的事件都会被派发;如果event_type 是0,所有接收者为receiver的事件都会被派发。(If receiver is null, the events of event_type are sent for all objects. If event_type is 0, all the events are sent for receiver.)
注意:这个函数只能在接受者receiver所属的线程中被调用,不能跨线程。


QObject类中与Qt的事件机制相关的几个重要函数


bool QObject::event(QEvent * e) [virtual]


这个虚函数会接收事件并处理。如果传递来的事件被识别并处理则返回true。event()函数可以被重写以实现所需的功能。


void QObject::timerEvent(QTimerEvent * event) [virtual protected]


这个事件handler可以在子类中被重写以接收定时器事件。
QTimer提供了更高水平的接口来实现定时器功能,并且包含了更多与定时器相关的信息。定时器事件会通过event参数传递进来。


void QObject::installEventFilter(QObject * filterObj)


为调用者在filterObj上安装一个事件过滤器。示例:
monitoredObj->installEventFilter(filterObj);
所有送达接收者的事件都会被接收者的事件过滤器接收审查。过滤器可以打断事件的派发或者将它派发给接收者。本函数中的事件过滤器filterObj会通过它自己的eventFilter()函数(译注:这个函数下面会介绍)接收事件。如果事件被过滤掉了,eventFilter()要返回true;否则返回假。
如果一个对象上被安装上了多个过滤器,最后安装的事件过滤器会第一个生效。
下面是一个KeyPressEater类,他会吃掉他监视对象的按键按下的消息。

class KeyPressEater : public QObject
{
    Q_OBJECT
    ...



protected:
    bool eventFilter(QObject *obj, QEvent *event);
};

bool KeyPressEater::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        qDebug("Ate key press %d", keyEvent->key());
        return true;
    } else {
        // standard event processing
        return QObject::eventFilter(obj, event);
    }
}

下面是如何将它安装在两个窗口上的方法:

KeyPressEater *keyPressEater = new KeyPressEater(this);
QPushButton *pushButton = new QPushButton(this);
QListView *listView = new QListView(this);

pushButton->installEventFilter(keyPressEater);
listView->installEventFilter(keyPressEater);

举个例子,QShortcut类就是使用这个方法来拦截快捷键按下的事件的。
警告:如果你在eventFilter()函数中将接收者receiver 给delete了,要保证这时返回值是true。否则如果返回false,Qt会把事件继续发送给被delete掉的对象,这样程序就崩溃了。
注意过滤器对象filterObj(被安装过滤器的对象)必须与当前对象(被监视对象)在同一个线程中。如果filterObj在不同的线程中,这个函数就不起任何作用。如果filterObj或者当前对象在调用完本函数后被移动到了不同的线程中,那事件过滤器在两个对象重新移到相同线程之后才会被调用(如果过滤器还没有被remove的话)。
译注:关于remove,参见下面的QObject::removeEventFilter。
译注:为什么不允许跨线程安装过滤器?款线程的过滤器在具体实现中的瓶颈是什么?


附:void QObject::removeEventFilter(QObject * obj)
移除当前对象安装在obj上的事件过滤器。如果要被remove的事件过滤器并没有被安装过,本次remove请求会被忽略。
当前对象的所有事件过滤器在当前对象被销毁时都会被自动移除。
任何时候调用本函数来移除过滤器都是安全的,甚至当事件过滤器正在工作时(也就是在eventFilter()函数中调用本函数)


bool QObject::eventFilter(QObject * watched, QEvent * event) [virtual]


如果当前对象被其他对象安装过过滤器,则本函数会对事件进行过滤。
译注: 上面的installEventFilter和removeEventFilter都是由被监控的对象调用,用来在监控者身上为自己安装或移除过滤器;而本函数eventFilter则是被监控者调用,处理被监控者的事件。
当你重写这个函数时,如果你想将事件过滤掉,就是不想让他被处理的话,就返回true;否则返回false。
示例:

class MainWindow : public QMainWindow
{
public:
    MainWindow();

protected:
    bool eventFilter(QObject *obj, QEvent *ev);

private:
    QTextEdit *textEdit;
};

MainWindow::MainWindow()
{
    textEdit = new QTextEdit;
    setCentralWidget(textEdit);

    textEdit->installEventFilter(this);
}

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == textEdit) {
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
            qDebug() << "Ate key press" << keyEvent->key();
            return true;
        } else {
            return false;
        }
    } else {
        // pass the event on to the parent class
        return QMainWindow::eventFilter(obj, event);
    }
}

注意在上面的例子中,没有被处理的事件被传递到了基类的eventFilter()中,这是因为基类的eventFilter()中可能也有一些有特定作用的处理代码。
警告:如果你在这个函数中将receiver给delete掉了,要确保返回值是true。否则,Qt可能会将event继续传给被delete掉的对象而导致程序崩溃。


附加内容(非译):


exec():
除了QEventLoop,还有QApplication、QCoreApplication、QGuiApplication、QThread、QMessageBox、QMenu等等多个类都有exec()函数,不过他们的实现都是基于QEventLoop类的。
processEvents():除了QEventLoop,还有QAbstractEventDispatcher和QCoreApplication都有这个函数。
notify():只有QCoreApplication、QGuiApplication、QApplication这些与应用相关的类中有这个函数。(还有音频输入输出相关的两个类也有此函数,不过这里只讨论与Qt架构关系密切的类)
postEvent():只有QCoreApplication类中有这个函数。(还有状态机类,不过不讨论他)
sendEvent():只有QCoreApplication类中有这个函数。(其他包含这个函数类不重要)



事件机制总结(非译)

总结一下事件的传递过程。
直接传递事件使用sendEvent,队列传递用postEvent;
无论直接传递还是队列传递,最终在传送事件时都是调用notify;
notify中会调用事件接收者的event函数来处理事件;
event函数中会首先将送来的事件交给事件过滤器过滤,这时会执行过滤器对象的eventFilter函数;
如果事件没有被过滤,控制权会再次回到event函数中,event对于这些事件会根据事件类型调用专门的事件函数(比如paintEvent(), mousePressEvent()等)来处理。



你可能感兴趣的:(qt,event,事件循环,QMetaObject,QtAssitant,Qt对象模型)