1.事件定義
事件(event)是由系統或者 Qt 本身在不同的時刻發出的,當用戶按下滑鼠,敲下鍵盤,或者是視窗需要重新繪制的時候,都會發出一個相應的事件,一些事件是在對用戶操作做出回應的時候發出,如鍵盤事件等;另一些事件則是由系統自動發出,如計時器事件,
2.事件與信號槽
一般來說,使用 Qt 編程時,我們并不會把主要精力放在事件上,因為在 Qt 中,需要我們關心的事件總會發出一個信號,比如,我們關心的是 QPushButton 的滑鼠點擊,但我們不需要關心這個滑鼠點擊事件,而是關心它的 clicked()信號,
信號槽: signal 由具體物件發出,然后會馬上交給由connect 函式連接的 slot 進行處理,
事件: Qt 使用一個事件佇列對所有發出的事件進行維護,當新的事件產生時,會被追加到事件佇列的尾部,前一個事件完成后,取出后面的事件進行處理,但是,必要的時候,Qt 的事件也是可以不進入事件佇列,而是直接處理的,并且,事件還可以使用“事件過濾器”進行過濾,
總的來說,如果我們使用組件,我們關心的是信號槽;如果我們自定義組件,我們關心的是事件,因為我們可以通過事件來改變組件的默認操作,比如,如果我們要自定義一個 QPushButton,那么我們就需要重寫它的滑鼠點擊事件和鍵盤處理事件,并且在恰當的時候發出 clicked()信號,
3.事件回圈、事件處理函式
我們在 main 函式里面創建了一個 QApplication 物件,然后呼叫了它的 exec()函式,其實,這個函式就是開始 Qt 的事件回圈,在執行 exec()函式之后,程式將進入事件回圈來監聽應用程式的事件,
當事件發生時,Qt 將創建一個事件物件,Qt 的所有事件都繼承于 QEvent 類,在事件物件創建完畢后,Qt 將這個事件物件傳遞給 QObject 的 event()函式,event()函式并不直接處理事件,而是按照事件物件的型別分派給特定的事件處理函式(event handler),
例如在所有組件的父類 QWidget 中,定義了很多事件處理函式,如 keyPressEvent()、keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent ()、mousePressEvent()、mouseReleaseEvent()等,這些函式都是 protected virtual 的,也就是說,我們應該在子類中重定義這些函式,
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
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 app(argc, argv);
EventLabel *label = new EventLabel;
label->setWindowTitle("MouseEvent Demo");
label->resize(300, 200);
label->show();
return app.exec();
}
這里我們繼承了 QLabel 類,重寫了 mousePressEvent、mouseMoveEvent 和 MouseReleaseEvent三個函式,我們并沒有添加什么功能,只是在滑鼠按下(press)、滑鼠移動(move)和滑鼠釋放(release)時把坐標顯示在這個 Label 上面,
4.事件接受與忽略
前面的代碼,我們在子類中重寫了事件函式,以便讓這些子類按照我們的需要完成某些功能,就像下面的代碼:
void MyLabel::mousePressEvent(QMouseEvent * event)
{
if(event->button() == Qt::LeftButton) {
// do something
} else {
QLabel::mousePressEvent(event);
}
}
上面的代碼和前面類似,在滑鼠按下的事件中檢測,如果按下的是左鍵,做我們的處理作業,如果不是左鍵,則呼叫父類的函式,這在某種程度上說,是把事件向上傳遞給父類去回應,也就是說,我們在子類中“忽略”了這個事件,
我們可以把 Qt 的事件傳遞看成鏈狀:如果子類沒有處理這個事件,就會繼續向其他類傳遞,其實,Qt的事件物件都有一個 accept()函式和 ignore()函式,正如它們的名字,前者用來告訴 Qt,事件處理函式“接收”了這個事件,不要再傳遞;后者則告訴 Qt,事件處理函式“忽略”了這個事件,需要繼續傳遞,尋找另外的接受者,在事件處理函式中,可以使用 isAccepted()來查詢這個事件是不是已經被接收了,
事實上,我們很少使用 accept()和 ignore()函式,而是像上面的示例一樣,如果希望忽略事件,只要呼叫父類的回應函式即可,
Qt 中的事件大部分是 protected 的,因此,重寫的函式必定存在著其父類中的回應函式,這個方法是可行的,為什么要這么做呢?因為我們無法確認父類中的這個處理函式沒有操作,如果我們在子類中直接忽略事件,Qt 不會再去尋找其他的接受者,那么父類的操作也就不能進行,這可能會有潛在的危險,
在一個情形下,我們必須使用 accept()和 ignore()函式,那就是在視窗關閉的時候,如果你在視窗關閉時需要有個詢問對話框,那么就需要這么去寫:
void MainWindow::closeEvent(QCloseEvent * event)
{
if(continueToClose()) {
event->accept();
} else {
event->ignore();
}
}
bool MainWindow::continueToClose()
{
if(QMessageBox::question(this,
tr("Quit"),
tr("Are you sure to quit this application?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No)
== QMessageBox::Yes) {
return true;
} else {
return false;
}
}
這樣,我們經過詢問之后才能正常退出程式,
5.event()函式
事件物件創建完畢后,Qt 將這個事件物件傳遞給 QObject的 event()函式,event()函式并不直接處理事件,而是將這些事件物件按照它們不同的型別,分發給不同的事件處理器(event handler),
event()函式主要用于事件的分發,所以,如果你希望在事件分發之前做一些操作,那么,就需要注意這個 event()函式了,為了達到這種目的,我們可以重寫 event()函式,
例如,如果你希望在視窗中的tab 鍵按下時將焦點移動到下一組件,而不是讓具有焦點的組件處理,那么你就可以繼承 QWidget,并重寫它的 event()函式,已達到這個目的:
bool MyWidget::event(QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
// 處理 Tab 鍵
return true;
}
}
return QWidget::event(event);
}
event()函式接受一個 QEvent 物件,也就是需要這個函式進行轉發的物件,為了進行轉發,必定需要有一系列的型別判斷,這就可以呼叫 QEvent 的 type()函式,其回傳值是 QEvent::Type 型別的列舉,
我們處理過自己需要的事件后,可以直接 return 回去,對于其他我們不關心的事件,需要呼叫父類的 event()函式繼續轉發,否則這個組件就只能處理我們定義的事件了,
event()函式回傳值是 bool 型別,如果傳入的事件已被識別并且處理,回傳 true,否則回傳 false,如果回傳值是 true,QApplication 會認為這個事件已經處理完畢,會繼續處理事件佇列中的下一事件;如果回傳值是 false,QApplication 會嘗試尋找這個事件的下一個處理函式,
event()函式的回傳值和事件的 accept()和 ignore()函式不同,accept()和ignore()函式用于不同的事件處理器之間的溝通,例如判斷這一事件是否處理;event()函式的回傳值主要是通知QApplication 的 notify()函式是否處理下一事件,
為了更加明晰這一點,我們來看看 QWidget 的event()函式是如何定義的:
bool QWidget::event(QEvent *event) {
switch (e->type()) {
case QEvent::KeyPress:
keyPressEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
case QEvent::KeyRelease:
keyReleaseEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
// more...
}
return true;
}
QWidget 的 event()函式使用一個巨大的 switch 來判斷 QEvent 的 type,并且分發給不同的事件處理函式,在事件處理函式之后,使用這個事件的 isAccepted()方法,獲知這個事件是不是被接受,如果沒有被接受則 event()函式立即回傳 false,否則回傳 true,
另外一個必須重寫 event()函式的情形是有自定義事件的時候,如果你的程式中有自定義事件,則必須重寫 event()函式以便將自定義事件進行分發,否則你的自定義事件永遠也不會被呼叫,
6.事件過濾器
Qt 創建了 QEvent 事件物件之后,會呼叫 QObject 的 event()函式做事件的分發,有時候,你可能需要在呼叫 event()函式之前做一些另外的操作,比如,對話框上某些組件可能并不需要回應回車按下的事件,此時,你就需要重新定義組件的 event()函式,如果組件很多,就需要重寫很多次 event()函式,這顯然沒有效率,為此,你可以使用一個事件過濾器,來判斷是否需要呼叫 event()函式,
QOjbect 有一個 eventFilter()函式,用于建立事件過濾器,這個函式的簽名如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )
如果 watched 物件安裝了事件過濾器,這個函式會被呼叫并進行事件過濾,然后才輪到組件進行事件處理,在重寫這個函式時,如果你需要過濾掉某個事件,例如停止對這個事件的回應,需要回傳 true,
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);
}
}
上面的例子中為 MainWindow 建立了一個事件過濾器,為了過濾某個組件上的事件,首先需要判斷這個物件是哪個組件,然后判斷這個事件的型別,
例如,我不想讓 textEdit 組件處理鍵盤事件,于是就首先找到這個組件,如果這個事件是鍵盤事件,則直接回傳 true,也就是過濾掉了這個事件,其他事件還是要繼續處理,所以回傳 false,對于其他組件,我們并不保證是不是還有過濾器,于是最保險的辦法是呼叫父類的函式,
在創建了過濾器之后,下面要做的是安裝這個過濾器,安裝過濾器需要呼叫 installEventFilter()函式,這個函式的簽名如下:
void QObject::installEventFilter ( QObject * filterObj )
這個函式是 QObject 的一個函式,因此可以安裝到任何 QObject 的子類,并不僅僅是 UI 組件,這個函式接收一個 QObject 物件,呼叫了這個函式安裝事件過濾器的組件會呼叫 filterObj 定義的eventFilter()函式,
例如,textField.installEventFilter(obj),則如果有事件發送到textField 組件是,會先呼叫 obj->eventFilter()函式,然后才會呼叫 textField.event(),
當然,你也可以把事件過濾器安裝到 QApplication 上面,這樣就可以過濾所有的事件,已獲得更大的控制權,不過,這樣做的后果就是會降低事件分發的效率,
如果一個組件安裝了多個過濾器,則最后一個安裝的會最先呼叫,類似于堆疊的行為,
注意: 如果你在事件過濾器中 delete 了某個接收組件,務必將回傳值設為 true,否則,Qt 還是會將事件分發給這個接收組件,從而導致程式崩潰,
事件過濾器和被安裝的組件必須在同一執行緒,否則,過濾器不起作用,另外,如果在 install 之后,這兩個組件到了不同的執行緒,那么,只有等到二者重新回到同一執行緒的時候過濾器才會有效,
事件的呼叫最終都會呼叫 QCoreApplication 的 notify()函式,因此,最大的控制權實際上是重寫QCoreApplication 的 notify()函式,由此可以看出,Qt 的事件處理實際上是分層五個層次:
- 重定義事件處理函式
- 重定義 event()函式
- 為單個組件安裝事件過濾器
- 為 QApplication 安裝事件過濾器
- 重定義 QCoreApplication 的 notify()函式
這幾個層次的控制權是逐層增大的,
7.自定義事件
Qt 允許創建自己的事件型別,這在多執行緒的程式中尤其有用,當然,也可以用在單執行緒的程式中,作為一種物件間通訊的機制,那么,為什么需要使用事件,而不是使用信號槽呢?主要原因是,事件的分發既可以是同步的,又可以是異步的,而函式的呼叫或者說是槽的回呼總是同步的,事件的另外一個好處是,它可以使用過濾器,
Qt 中的自定義事件很簡單,同其他類似的庫的使用很相似,都是要繼承一個類進行擴展,在 Qt 中,你需要繼承的類是 QEvent,
繼承 QEvent 類,你需要提供一個QEvent::Type 型別的引數,作為自定義事件的型別值,這里的QEvent::Type 型別是 QEvent 里面定義的一個 enum,因此,你是可以傳遞一個 int 的,重要的是,你的事件型別不能和已經存在的 type 值重復,否則會有不可預料的錯誤發生!因為系統會將你的事件當做系統事件進行派發和呼叫,
在 Qt 中,系統將保留0 - 999的值,也就是說,你的事件 type 要大于999. 具體來說,你的自定義事件的 type 要在 QEvent::User 和 QEvent::MaxUser 的范圍之間,其中,QEvent::User 值是1000,QEvent::MaxUser 的值是65535,從這里知道,你最多可以定義64536個事件,相信這個數字已經足夠大了!
但是,即便如此,也只能保證用戶自定義事件不能覆寫系統事件,并不能保證自定義事件之間不會被覆寫,為了解決這個問題,Qt 提供了一個函式:registerEventType(),用于自定義事件的注冊,該函式簽名如下:
static int QEvent::registerEventType ( int hint = -1 );
函式是 static 的,因此可以使用 QEvent 類直接呼叫,函式接受一個 int 值,其默認值為-1,回傳值是創建的這個 Type 型別的值,如果 hint 是合法的,不會發生任何覆寫,則會回傳這個值;如果hint 不合法,系統會自動分配一個合法值并回傳,因此,使用這個函式即可完成 type 值的指定,這個函式是執行緒安全的,因此不必另外添加同步,
你可以在 QEvent 子類中添加自己的事件所需要的資料,然后進行事件的發送,Qt 中提供了兩種發送方式:
static bool QCoreApplication::sendEvent(QObjecy receiver, QEvent event):事件被 QCoreApplication 的 notify()函式直接發送給 receiver 物件,回傳值是事件處理函式的回傳值,使用這個函式必須要在堆疊上創建物件,例如:
QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
QApplication::sendEvent(mainWindow, &event);
static bool QCoreApplication::postEvent(QObject receiver, QEvent event):事件被 QCoreApplication 追加到事件串列的最后,并等待處理,該函式將事件追加后會立即回傳,并且注意,該函式是執行緒安全的,另外一點是,使用這個函式必須要在堆上創建物件,例如:
QApplication::postEvent(object, new MyEvent(QEvent::registerEventType(2048)));
這個物件不需要手動 delete,Qt 會自動 delete 掉!因此,如果在 post 事件之后呼叫 delete,程式可能會崩潰,另外,postEvent()函式還有一個多載的版本,增加一個優先級引數,具體請參見API,通過呼叫 sendPostedEvent()函式可以讓已提交的事件立即得到處理,
如果要處理自定義事件,可以重寫 QObject 的 customEvent()函式,該函式接收一個 QEvent 物件作為引數,可以像前面介紹的重寫 event()函式的方法去重寫這個函式:
void CustomWidget::customEvent(QEvent *event) {
CustomEvent *customEvent = static_cast<CustomEvent *>(event);
// ....
}
另外,你也可以通過重寫 event()函式來處理自定義事件:
bool CustomWidget::event(QEvent *event) {
if (event->type() == MyCustomEventType) {
CustomEvent *myEvent = static_cast<CustomEvent *>(event);
// processing...
return true;
}
return QWidget::event(event);
}
這兩種辦法都是可行的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286895.html
標籤:其他
下一篇:用戶登錄 JWT TOKEN
