Qt消息機制和事件(一)


一,事件

  事件(event)是由系統或者 Qt 本身在不同的時刻發出的。當用戶按下鼠標、敲下鍵盤,或者是窗口需要重新繪制的時候,都會發出一個相應的事件。一些事件在對用戶操作做出響應時發出,如鍵盤事件等;另一些事件則是由系統自動發出,如計時器事件。

  Qt 程序需要在main()函數創建一個QApplication對象,然后調用它的exec()函數。這個函數就是開始 Qt 的事件循環。在執行exec()函數之后,程序將進入事件循環來監聽應用程序的事件。當事件發生時,Qt 將創建一個事件對象。Qt 中所有事件類都繼承於QEvent。在事件對象創建完畢后,Qt 將這個事件對象傳遞給QObject的event()函數。event()函數並不直接處理事件,而是按照事件對象的類型分派給特定的事件處理函數event handler)。

在所有組件的父類QWidget中,定義了很多事件處理的回調函數,如

n keyPressEvent()

n keyReleaseEvent()

n mouseDoubleClickEvent()

n mouseMoveEvent()

n mousePressEvent()

n mouseReleaseEvent() 等。

  這些函數都是 protected virtual 的,也就是說,我們可以在子類中重新實現這些函數。下面來看一個例子:

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()三個函數。我們並沒有添加什么功能,只是在鼠標按下(press)、鼠標移動(move)和鼠標釋放(release)的時候,把當前鼠標的坐標值顯示在這個Label上面。由於QLabel是支持 HTML 代碼的,因此我們直接使用了 HTML 代碼來格式化文字。

   QString的arg()函數可以自動替換掉QString中出現的占位符。其占位符以 % 開始,后面是占位符的位置,例如 %1,%2 這種。

QString("[%1, %2]").arg(x).arg(y);

語句將會使用x替換 %1,y替換 %2,因此,生成的QString為[x, y]。

  在mouseReleaseEvent()函數中,我們使用了另外一種QString的構造方法。我們使用類似 C 風格的格式化函數sprintf()來構造QString。

運行上面的代碼,當我們點擊了一下鼠標之后,label 上將顯示鼠標當前坐標值。

 

為什么要點擊鼠標之后才能在mouseMoveEvent()函數中顯示鼠標坐標值?

  這是因為QWidget中有一個mouseTracking屬性,該屬性用於設置是否追蹤鼠標。只有鼠標被追蹤時,mouseMoveEvent()才會發出。如果mouseTracking是 false(默認即是),組件在至少一次鼠標點擊之后,才能夠被追蹤,也就是能夠發出mouseMoveEvent()事件。如果mouseTracking為 true,則mouseMoveEvent()直接可以被發出。

知道了這一點,我們就可以在main()函數中添加如下代碼:

label->setMouseTracking(true);

在運行程序就沒有這個問題了。

二,event()

  事件對象創建完畢后,Qt 將這個事件對象傳遞給QObject的event()函數。event()函數並不直接處理事件,而是將這些事件對象按照它們不同的類型,分發給不同的事件處理器(event handler)。

  如上所述,event()函數主要用於事件的分發。所以,如果你希望在事件分發之前做一些操作,就可以重寫這個event()函數了。例如,我們希望在一個QWidget組件中監聽 tab 鍵的按下,那么就可以繼承QWidget,並重寫它的event()函數,來達到這個目的:

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()函數,這個函數有一個QEvent對象作為參數,也就是需要轉發的事件對象。函數返回值是 bool 類型。

  如果傳入的事件已被識別並且處理,則需要返回 true,否則返回 false。如果返回值是 true,那么 Qt 會認為這個事件已經處理完畢,不會再將這個事件發送給其它對象,而是會繼續處理事件隊列中的下一事件。

  在event()函數中,調用事件對象的accept()和ignore()函數是沒有作用的,不會影響到事件的傳播

  我們可以通過使用QEvent::type()函數可以檢查事件的實際類型,其返回值是QEvent::Type類型的枚舉。我們處理過自己感興趣的事件之后,可以直接返回 true,表示我們已經對此事件進行了處理;對於其它我們不關心的事件,則需要調用父類的event()函數繼續轉發,否則這個組件就只能處理我們定義的事件了。為了測試這一種情況,我們可以嘗試下面的代碼:

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()函數,卻沒有調用父類的同名函數。這樣,我們的組件就只能處理 Tab 鍵,再也無法輸入任何文本,也不能響應其它事件,比如鼠標點擊之后也不會有光標出現。這是因為我們只處理的KeyPress類型的事件,並且如果不是KeyPress事件,則直接返回 false,鼠標事件根本不會被轉發,也就沒有了鼠標事件。

  通過查看QObject::event()的實現,我們可以理解,event()函數同前面的章節中我們所說的事件處理器有什么聯系:

//!!! 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()函數的源代碼(Qt 4 的版本也是類似的)。我們可以看到,同前面我們所說的一樣,Qt 也是使用QEvent::type()判斷事件類型,然后調用了特定的事件處理器。比如,如果event->type()返回值是QEvent::Timer,則調用timerEvent()函數。可以想象,QWidget::event()中一定會有如下的代碼:

switch (event->type()) {

    case QEvent::MouseMove:

        mouseMoveEvent((QMouseEvent*)event);

        break;

    // ...

}

  事實也的確如此。timerEvent()和mouseMoveEvent()這樣的函數,就是我們前面章節所說的事件處理器 event handler。也就是說,event()函數中實際是通過事件處理器來響應一個具體的事件。這相當於event()函數將具體事件的處理“委托”給具體的事件處理器。而這些事件處理器是 protected virtual 的,因此,我們重寫了某一個事件處理器,即可讓 Qt 調用我們自己實現的版本。

  由此可以見,event()是一個集中處理不同類型的事件的地方。如果你不想重寫一大堆事件處理器,就可以重寫這個event()函數,通過QEvent::type()判斷不同的事件。鑒於重寫event()函數需要十分小心注意父類的同名函數的調用,一不留神就可能出現問題,所以一般還是建議只重寫事件處理器(當然,也必須記得是不是應該調用父類的同名處理器)。這其實暗示了event()函數的另外一個作用:屏蔽掉某些不需要的事件處理器。正如我們前面的CustomTextEdit例子看到的那樣,我們創建了一個只能響應 tab 鍵的組件。這種作用是重寫事件處理器所不能實現的。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM