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()等)來處理。


發佈了51 篇原創文章 · 獲贊 76 · 訪問量 82萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章