Qt消息机制和事件概述(一)
一,事件
事件(event)是由系統(tǒng)或者 Qt 本身在不同的時刻發(fā)出的。當(dāng)用戶按下鼠標(biāo)、敲下鍵盤,或者是窗口需要重新繪制的時候,都會發(fā)出一個相應(yīng)的事件。一些事件在對用戶操作做出響應(yīng)時發(fā)出,如鍵盤事件等;另一些事件則是由系統(tǒng)自動發(fā)出,如計時器事件。
Qt 程序需要在main()函數(shù)創(chuàng)建一個QApplication對象,然后調(diào)用它的exec()函數(shù)。這個函數(shù)就是開始 Qt 的事件循環(huán)。在執(zhí)行exec()函數(shù)之后,程序?qū)⑦M入事件循環(huán)來監(jiān)聽?wèi)?yīng)用程序的事件。當(dāng)事件發(fā)生時,Qt 將創(chuàng)建一個事件對象。Qt 中所有事件類都繼承于QEvent。在事件對象創(chuàng)建完畢后,Qt 將這個事件對象傳遞給QObject的event()函數(shù)。event()函數(shù)并不直接處理事件,而是按照事件對象的類型分派給特定的事件處理函數(shù)(event handler)。
在所有組件的父類QWidget中,定義了很多事件處理的回調(diào)函數(shù),如
n?keyPressEvent()
n?keyReleaseEvent()
n?mouseDoubleClickEvent()
n?mouseMoveEvent()
n?mousePressEvent()
n?mouseReleaseEvent()?等。
這些函數(shù)都是?protected virtual 的,也就是說,我們可以在子類中重新實現(xiàn)這些函數(shù)。下面來看一個例子:
class EventLabel : public QLabel
{
protected:
????void mouseMoveEvent(QMouseEvent *event);
????void mousePressEvent(QMouseEvent *event);
????void mouseReleaseEvent(QMouseEvent *event);
};
?
void EventLabel::mouseMoveEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Move: (%1, %2)
</h1></center>").arg(QString::number(event->x()),
????????????QString::number(event->y())));
}
?
void EventLabel::mousePressEvent(QMouseEvent *event)
{
????this->setText(QString("<center><h1>Press:(%1, %2)
</h1></center>").arg(QString::number(event->x()),
????????????????QString::number(event->y())));
}
?
void EventLabel::mouseReleaseEvent(QMouseEvent *event)
{
????QString msg;
????msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>",
????????????????event->x(), event->y());
????this->setText(msg);
}
?
int main(int argc, char *argv[])
{
????QApplication a(argc, argv);
?
????EventLabel *label = new EventLabel;
????label->setWindowTitle("MouseEvent Demo");
????label->resize(300, 200);
????label->show();
?
????return a.exec();
}
EventLabel繼承了QLabel,覆蓋了mousePressEvent()、mouseMoveEvent()和MouseReleaseEvent()三個函數(shù)。我們并沒有添加什么功能,只是在鼠標(biāo)按下(press)、鼠標(biāo)移動(move)和鼠標(biāo)釋放(release)的時候,把當(dāng)前鼠標(biāo)的坐標(biāo)值顯示在這個Label上面。由于QLabel是支持 HTML 代碼的,因此我們直接使用了 HTML 代碼來格式化文字。
?QString的arg()函數(shù)可以自動替換掉QString中出現(xiàn)的占位符。其占位符以 % 開始,后面是占位符的位置,例如 %1,%2 這種。
QString("[%1, %2]").arg(x).arg(y);
語句將會使用x替換 %1,y替換 %2,因此,生成的QString為[x, y]。
在mouseReleaseEvent()函數(shù)中,我們使用了另外一種QString的構(gòu)造方法。我們使用類似 C 風(fēng)格的格式化函數(shù)sprintf()來構(gòu)造QString。
運行上面的代碼,當(dāng)我們點擊了一下鼠標(biāo)之后,label 上將顯示鼠標(biāo)當(dāng)前坐標(biāo)值。
?
為什么要點擊鼠標(biāo)之后才能在mouseMoveEvent()函數(shù)中顯示鼠標(biāo)坐標(biāo)值?
這是因為QWidget中有一個mouseTracking屬性,該屬性用于設(shè)置是否追蹤鼠標(biāo)。只有鼠標(biāo)被追蹤時,mouseMoveEvent()才會發(fā)出。如果mouseTracking是 false(默認(rèn)即是),組件在至少一次鼠標(biāo)點擊之后,才能夠被追蹤,也就是能夠發(fā)出mouseMoveEvent()事件。如果mouseTracking為 true,則mouseMoveEvent()直接可以被發(fā)出。
知道了這一點,我們就可以在main()函數(shù)中添加如下代碼:
label->setMouseTracking(true);
在運行程序就沒有這個問題了。
二,event()
事件對象創(chuàng)建完畢后,Qt 將這個事件對象傳遞給QObject的event()函數(shù)。event()函數(shù)并不直接處理事件,而是將這些事件對象按照它們不同的類型,分發(fā)給不同的事件處理器(event handler)。
如上所述,event()函數(shù)主要用于事件的分發(fā)。所以,如果你希望在事件分發(fā)之前做一些操作,就可以重寫這個event()函數(shù)了。例如,我們希望在一個QWidget組件中監(jiān)聽 tab 鍵的按下,那么就可以繼承QWidget,并重寫它的event()函數(shù),來達(dá)到這個目的:
bool CustomWidget::event(QEvent *e)
{
????if (e->type() == QEvent::KeyPress) {
????????QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
????????if (keyEvent->key() == Qt::Key_Tab) {
????????????qDebug() << "You press tab.";
????????????return true;
????????}
????}
????return QWidget::event(e);
}
CustomWidget是一個普通的QWidget子類。我們重寫了它的event()函數(shù),這個函數(shù)有一個QEvent對象作為參數(shù),也就是需要轉(zhuǎn)發(fā)的事件對象。函數(shù)返回值是 bool 類型。
如果傳入的事件已被識別并且處理,則需要返回?true,否則返回 false。如果返回值是 true,那么 Qt 會認(rèn)為這個事件已經(jīng)處理完畢,不會再將這個事件發(fā)送給其它對象,而是會繼續(xù)處理事件隊列中的下一事件。
在event()函數(shù)中,調(diào)用事件對象的accept()和ignore()函數(shù)是沒有作用的,不會影響到事件的傳播。
我們可以通過使用QEvent::type()函數(shù)可以檢查事件的實際類型,其返回值是QEvent::Type類型的枚舉。我們處理過自己感興趣的事件之后,可以直接返回 true,表示我們已經(jīng)對此事件進行了處理;對于其它我們不關(guān)心的事件,則需要調(diào)用父類的event()函數(shù)繼續(xù)轉(zhuǎn)發(fā),否則這個組件就只能處理我們定義的事件了。為了測試這一種情況,我們可以嘗試下面的代碼:
bool CustomTextEdit::event(QEvent *e)
{
???if (e->type() == QEvent::KeyPress)
{
????????QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
???????if (keyEvent->key() == Qt::Key_Tab)
{
????????????qDebug() << "You press tab.";
????????????return true;
???????}
????}
????return false;
}
CustomTextEdit是QTextEdit的一個子類。我們重寫了其event()函數(shù),卻沒有調(diào)用父類的同名函數(shù)。這樣,我們的組件就只能處理 Tab 鍵,再也無法輸入任何文本,也不能響應(yīng)其它事件,比如鼠標(biāo)點擊之后也不會有光標(biāo)出現(xiàn)。這是因為我們只處理的KeyPress類型的事件,并且如果不是KeyPress事件,則直接返回 false,鼠標(biāo)事件根本不會被轉(zhuǎn)發(fā),也就沒有了鼠標(biāo)事件。
通過查看QObject::event()的實現(xiàn),我們可以理解,event()函數(shù)同前面的章節(jié)中我們所說的事件處理器有什么聯(lián)系:
//!!! Qt5
bool QObject::event(QEvent *e)
{
????switch (e->type()) {
????case QEvent::Timer:
????????timerEvent((QTimerEvent*)e);
????????break;
?
????case QEvent::ChildAdded:
????case QEvent::ChildPolished:
????case QEvent::ChildRemoved:
????????childEvent((QChildEvent*)e);
????????break;
????// ...
????default:
????????if (e->type() >= QEvent::User) {
????????????customEvent(e);
????????????break;
????????}
????????return false;
????}
????return true;
}
這是?Qt 5 中QObject::event()函數(shù)的源代碼(Qt 4 的版本也是類似的)。我們可以看到,同前面我們所說的一樣,Qt 也是使用QEvent::type()判斷事件類型,然后調(diào)用了特定的事件處理器。比如,如果event->type()返回值是QEvent::Timer,則調(diào)用timerEvent()函數(shù)。可以想象,QWidget::event()中一定會有如下的代碼:
switch (event->type()) {
????case QEvent::MouseMove:
????????mouseMoveEvent((QMouseEvent*)event);
????????break;
????// ...
}
事實也的確如此。timerEvent()和mouseMoveEvent()這樣的函數(shù),就是我們前面章節(jié)所說的事件處理器 event handler。也就是說,event()函數(shù)中實際是通過事件處理器來響應(yīng)一個具體的事件。這相當(dāng)于event()函數(shù)將具體事件的處理“委托”給具體的事件處理器。而這些事件處理器是 protected virtual 的,因此,我們重寫了某一個事件處理器,即可讓 Qt 調(diào)用我們自己實現(xiàn)的版本。
由此可以見,event()是一個集中處理不同類型的事件的地方。如果你不想重寫一大堆事件處理器,就可以重寫這個event()函數(shù),通過QEvent::type()判斷不同的事件。鑒于重寫event()函數(shù)需要十分小心注意父類的同名函數(shù)的調(diào)用,一不留神就可能出現(xiàn)問題,所以一般還是建議只重寫事件處理器(當(dāng)然,也必須記得是不是應(yīng)該調(diào)用父類的同名處理器)。這其實暗示了event()函數(shù)的另外一個作用:屏蔽掉某些不需要的事件處理器。正如我們前面的CustomTextEdit例子看到的那樣,我們創(chuàng)建了一個只能響應(yīng) tab 鍵的組件。這種作用是重寫事件處理器所不能實現(xiàn)的。
總結(jié)
以上是生活随笔為你收集整理的Qt消息机制和事件概述(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker 方式安装、运行 Nexus
- 下一篇: 解决:Error response fr