轉載:
https://blog.csdn.net/apollon_krj/article/category/6939539
https://blog.csdn.net/qq_41072190/article/category/7593738
在Qt中我們可以應用信號與槽對一些鼠標點擊的操作進行處理,如:
QPushbutton::clicked
QPushbutton::realsead
QPushbutton::pressed
而信號與槽的處理屬於事件的一種,產生一個信號可以認為是一個信號事件,而槽函數就是對於該信號事件進行處理的回調函數。由於信號與槽屬於事件,也就是說信號很強大,但是事件更強大。那么我們就有必要好好總結一下Qt的常用的一些事件了。
1、首先明確事件處理過程:
事件(event)是由系統或者Qt本身在不同時刻發出的。當用戶按下鼠標、敲下鍵盤,或者其它情況時候都會發出一個相應的事件。一些事件在對用戶操作做出相應時發出,如鍵盤事件等;另外一些則是由系統自動發出,如計時事件等。Qt程序需要在main()函數創建一個QApplication對象,然后調用它的exec()函數。這個函數就是開始Qt的事件循環。在執行exec()函數之后,程序將進入事件循環來監聽應用程序的事件,當事件發生時,Qt將創建一個事件對象。Qt中所有事件類都繼承自QEvent。在事件對象創建完畢之后,Qt將這個事件對象傳遞給QObject的event()函數。event()函數並不直接處理事件,而是按照事件對象的類型分派給指定的事件處理函數(event handler)進行處理。
2、常用事件(事件處理函數):
1 keyPressEvent() //鍵盤按下事件 2 keyReleaseEvent() //鍵盤釋放事件 3 mouseDoubleClickEvent() //鼠標雙擊事件 4 mouseMoveEvent() //鼠標移動事件 5 mousePressEvent() //鼠標按下事件 6 mouseReleaseEvent() //鼠標釋放事件 7 timerEvent() //定時器事件 8 dragEnterEvent() //拖拽進入當前窗口事件 9 dragLeaveEvent() //拖拽離開當前窗口事件 10 dragMoveEvent() //拖拽移動事件 11 enterEvent() //進入窗口區域事件 12 leaveEvent() //離開窗口區域事件 13 closeEvent() //關閉窗口事件
以上的事件是比較常用的一些事件。這些事件的回調函數都是虛函數,其成員屬性為”protected function”,在其基類中聲明,再到具體的派生類中進行父類虛函數的覆寫,以實現不同類中對於同一事件的不同處理,以上的虛函數在QWidget中基本都已聲明,我們在具體使用時只需要繼承QWidget,然后在QWidget的派生類中具體實現即可。
3、QTimerEvent定時器事件:
測試main()函數不變(基類QWidget,MyWidget繼承自QWidget):
#include "mywidget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MyWidget w; w.show(); return a.exec(); }
比如,我們采用timerEvent()實現兩個計數器:其中一個計數器的事件觸發的時間間隔為1s(計數到23s停止),另一個為0.5s(一直計數不停止)。分別顯示在不同Label上。
/*myWidget.h*/ #ifndef MYWIDGET_H #define MYWIDGET_H #include <QWidget> namespace Ui { class MyWidget; } class MyWidget : public QWidget { Q_OBJECT public: explicit MyWidget(QWidget *parent = 0); ~MyWidget(); protected: //定時器事件 void timerEvent(QTimerEvent *); private: Ui::MyWidget *ui; int timerID_fast; //區分不同定時器,類似於文件描述符 int timerID_slow; }; #endif // MYWIDGET_H
/*myWidget.cpp*/ #include "mywidget.h" #include "ui_mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent), ui(new Ui::MyWidget) { ui->setupUi(this); timerID_fast = this->startTimer(1000);//以毫秒為單位,每隔1秒觸發一次定時器 timerID_slow = this->startTimer(500); } void MyWidget::timerEvent(QTimerEvent *ev) { //如果觸發定時器的是編號為timerID_fast,則處理,否則處理timerID_slow對應的代碼 if(ev->timerId() == timerID_fast){ static int sec = 0; if(sec == 20){ //定時到20s時停止計時 killTimer(timerID_fast); } ui->labelCoordinate->setText(QString("<center><h1>Timer out:%1</h1></center>").arg(sec++)); //<center><h1>Timer out:%1</h1></center>為HTML的寫法,中間(center)標題大小(h1)顯示 } else if(ev->timerId() == timerID_slow){ static int sec = 0; ui->label->setText(QString("<center><h1>Timer out:%1</h1></center>").arg(sec++)); } } MyWidget::~MyWidget() { delete ui; }
結果如下:
對於定時器的操作就是設定了每個一段時間產生一個中斷,然后去執行我們在派生類中重新覆寫的虛函數(這里的虛函數是覆寫基類中的,所以其返回值、參數、函數名都和基類的函數名是相同的),顯然這是一個回調函數。
4、QMouseEvent鼠標事件:
鼠標事件應該算得上是GUI編程中用的最多的一個事件了,比如掃雷、棋類游戲等都對於鼠標事件的使用是相當頻繁的。而鼠標事件我們就拿press、release、move、enter、leave等幾個事件來說說:
QWidget.cpp以及QWidget.h必須要修改,保持創建QWidget時即可,添加Class文件為MyLabel。在標簽上顯示鼠標move的坐標、鼠標位於標簽內還是標簽外(當鼠標位於標簽內時,會顯示move的坐標,所以測試鼠標leave與enter時應注銷move的setText()),在qDebug中顯示鼠標按鍵是左鍵/右鍵/中鍵,
/*myLabel.h*/ #ifndef MYLABEL_H #define MYLABEL_H //需要對標簽進行提升,提升原本的類QLabel為MyLabel #include <QWidget> #include <QLabel> class MyLabel : public QLabel { Q_OBJECT public: explicit MyLabel(QWidget *parent = 0); protected: //鼠標點擊事件 void mousePressEvent(QMouseEvent *ev); //鼠標釋放事件 void mouseReleaseEvent(QMouseEvent *ev); //鼠標移動事件 void mouseMoveEvent(QMouseEvent *ev); //進入窗口(Label)區域 void enterEvent(QEvent *); //離開窗口(Label)區域 void leaveEvent(QEvent *); signals: public slots: }; #endif // MYLABEL_H
/*myLabel.cpp*/ #include "mylabel.h" #include <QMouseEvent> #include <qDebug> MyLabel::MyLabel(QWidget *parent) : QLabel(parent) { //設定追蹤鼠標,一開始運行就追蹤而不是需要先按下鍵才會追蹤 this->setMouseTracking(true); } //鼠標按下事件處理函數 void MyLabel::mousePressEvent(QMouseEvent *ev) { if(ev->button() == Qt::LeftButton){ qDebug() << "Left"; }else if(ev->button() == Qt::RightButton){ qDebug() << "Right"; }else if(ev->button() == Qt::MidButton){ qDebug() << "Middle"; } /* * QString str = QString("abc %1 ^_^ %2").arg(123456).arg("ABCDEF"); * str = abc 123456 ^_^ ABCDEF */ int i = ev->x(); int j = ev->y(); QString str = QString("<center><h1>Mouse Press:(%1, %2)</h1></center>").arg(i).arg(j); this->setText(str); } //鼠標抬起事件處理函數 void MyLabel::mouseReleaseEvent(QMouseEvent *ev) { QString str = QString("<center><h1>Mouse Release:(%1, %2)</h1></center>").arg(ev->x()).arg(ev->y()); this->setText(str); } //鼠標移動事件處理函數 void MyLabel::mouseMoveEvent(QMouseEvent *ev) { QString str = QString("<center><h1>Mouse Move:(%1, %2)</h1></center>").arg(ev->x()).arg(ev->y()); //this->setText(str); } //鼠標位於標簽內 void MyLabel::enterEvent(QEvent * ev) { QString str = QString("<center><h1>Mouse Enter</h1></center>"); this->setText(str); } //鼠標位於標簽外 void MyLabel::leaveEvent(QEvent *ev) { QString str = QString("<center><h1>Mouse Leave</h1></center>"); this->setText(str); }
5、QCloseEvent事件與QMessageBox使用:
在上例中QWidget加上鼠標關閉事件,點擊右上角關閉彈出QMessageBox::question窗口選擇是否關閉,該功能用到的accept()與ignore()兩個事件處理函數,第一個accept()是接收事件,之后事件就不再向下傳遞了,而ignore()則是忽略事件不做處理事件會傳遞給父組件(而不是基類)
void MyWidget::closeEvent(QCloseEvent *ev) { int ret = QMessageBox::question(this,"question","Close the Windows?"); if(ret == QMessageBox::Yes){ //關閉窗口 //接收事件,事件不再向下傳遞 ev->accept(); }else{ //不關閉窗口 //忽略事件,事件繼續給父組件傳遞,由於父組件沒有做關於close的接收操作 //所以不做關閉操作 ev->ignore(); } }
6、QKeyEvent鍵盤事件:
ev作為傳遞事件的參數其參數key()可以獲取引發事件(中斷)的是哪一個按鍵,與Qt的枚舉變量進行比對,來進行相應的操作:
void MyWidget::keyPressEvent(QKeyEvent *ev) { if(ev->key() >= Qt::Key_A && ev->key() <= Qt::Key_Z){ qDebug() << (char)ev->key(); } else{ qDebug() << ev->key(); } }
該鍵盤事件處理函數是對A~Z的字母進行識別並打印(不打印ASCII碼,而打印對應字符),而對於非A~Z的字符則打印ASCII碼:
常用的一些鍵的枚舉常量如下所示(我們不必要去記憶這些東西,只需要在幫助文檔查一下即可,但是還是有必要瀏覽一遍的):
Qt::Key_Escape 0x01000000 Qt::Key_Tab 0x01000001 Qt::Key_Backtab 0x01000002 Qt::Key_Backspace 0x01000003 Qt::Key_Return 0x01000004 Qt::Key_Enter 0x01000005 Typically located on the keypad. Qt::Key_Insert 0x01000006 Qt::Key_Delete 0x01000007 Qt::Key_Pause 0x01000008 The Pause/Break key (Note: Not related to pausing media) Qt::Key_Print 0x01000009 Qt::Key_SysReq 0x0100000a Qt::Key_Clear 0x0100000b Qt::Key_Home 0x01000010 Qt::Key_End 0x01000011 Qt::Key_Left 0x01000012 Qt::Key_Up 0x01000013 Qt::Key_Right 0x01000014 Qt::Key_Down 0x01000015 Qt::Key_PageUp 0x01000016 Qt::Key_PageDown 0x01000017 Qt::Key_Shift 0x01000020 Qt::Key_Control 0x01000021 On OS X, this corresponds to the Command keys. Qt::Key_Meta 0x01000022 On OS X, this corresponds to the Control keys. On Windows keyboards, this key is mapped to the Windows key. Qt::Key_Alt 0x01000023 Qt::Key_AltGr 0x01001103 On Windows, when the KeyDown event for this key is sent, the Ctrl+Alt modifiers are also set. Qt::Key_CapsLock 0x01000024 Qt::Key_NumLock 0x01000025 Qt::Key_ScrollLock 0x01000026 Qt::Key_F1 0x01000030 Qt::Key_F2 0x01000031 Qt::Key_F3 0x01000032 Qt::Key_F4 0x01000033 Qt::Key_F5 0x01000034 Qt::Key_F6 0x01000035 Qt::Key_F7 0x01000036 Qt::Key_F8 0x01000037 Qt::Key_F9 0x01000038 Qt::Key_F10 0x01000039 Qt::Key_F11 0x0100003a Qt::Key_F12 0x0100003b Qt::Key_0 0x30 Qt::Key_1 0x31 Qt::Key_2 0x32 Qt::Key_3 0x33 Qt::Key_4 0x34 Qt::Key_5 0x35 Qt::Key_6 0x36 Qt::Key_7 0x37 Qt::Key_8 0x38 Qt::Key_9 0x39 Qt::Key_A 0x41 Qt::Key_B 0x42 Qt::Key_C 0x43 Qt::Key_D 0x44 Qt::Key_E 0x45 Qt::Key_F 0x46 Qt::Key_G 0x47 Qt::Key_H 0x48 Qt::Key_I 0x49 Qt::Key_J 0x4a Qt::Key_K 0x4b Qt::Key_L 0x4c Qt::Key_M 0x4d Qt::Key_N 0x4e Qt::Key_O 0x4f Qt::Key_P 0x50 Qt::Key_Q 0x51 Qt::Key_R 0x52 Qt::Key_S 0x53 Qt::Key_T 0x54 Qt::Key_U 0x55 Qt::Key_V 0x56 Qt::Key_W 0x57 Qt::Key_X 0x58 Qt::Key_Y 0x59 Qt::Key_Z 0x5a
7、event()事件處理器與eventFilter()事件過濾器: 這兩個函數也是虛函數,均聲明在基類QObject中。
1 virtual bool event(QEvent * e)
2 virtual bool eventFilter(QObject * watched, QEvent * event)
有時候,我們需要對某個事件進行屏蔽,可以通過在其事件處理的中斷函數中進行操作,也可以在event()事件處理函數中進行操作,還可以在eventFilter()事件過濾器中進行操作,以上三種操作:eventFilter()事件過濾器中操作是比較方便的,可以說是指哪打哪,可以隨意過濾掉某一個類中的某一個事件。而event()也可以做到,但是event()函數主要功能並不是直接處理事件,而是按照事件對象的類型分派給指定的事件處理函數(event handler)進行處理。
這種關系可以表示為:
event()是一個根據不同事件做分類處理的功能,具體如下(就類似於switch…case語句或者if..else..語句),這是對原有event()虛函數的覆寫(屏蔽定時器,屏蔽除了Y按鍵的其它按鍵):
//事件處理器event()只在本控件有效,我們這里通過event()進行事件過濾 bool MyWidget::event(QEvent *ev) { //switch的寫法 //對於定時器進行功能進行覆寫,對鍵盤的Y鍵保留中斷,其他鍵忽略。其它時間仍按默認處理 //默認的是在event()的分發層(下一級)進行處理,而定時器的處理直接放在了event()函數中處理 //到了下一層定時器的處理發現return的是true就不再處理,鍵盤按鍵也是同理 switch(ev->type()){ case QEvent::Timer: //如果是定時器事件,返回true停止時間傳播,忽略定時器中斷 //timerEvent(static_cast<QTimerEvent*>(ev)); //不處理直接返回就是干掉了定時器 //取消注釋,則是將定時器事件提到event()函數中直接處理 return true; break; case QEvent::KeyPress: if(static_cast<QKeyEvent *>(ev)->key() == Qt::Key_Y){ return QWidget::event(ev); } return true; break; default: //默認的則是保留原有event()的處理方式 return QWidget::event(ev); break; } }
當然,用switch能寫出來用if…else…也可以,應根據需求進行選擇(當分支較多時switch是一個空間換取時間的做法(switch是隨機訪問會為每個case的指令塊生成一個起始地址標記,自然存在一個跳躍表的空間),而if…else…是一個時間效率不及switch的做法,但節省空間。而一般要用event屏蔽事件分支都比較少,二者就無太大區別)。關於if…else…與switch()case的效率比較參考blog:switch與ifelse的效率問題
//if...else...的寫法 bool MyWidget::event(QEvent *ev) { if(ev->type() == QEvent::Timer){ //定時器特殊處理:忽略 return true; } else if(ev->type() == QEvent::KeyPress){ //鍵盤處理:保留Y鍵,其它鍵忽略 if(static_cast<QKeyEvent *>(ev)->key() == Qt::Key_Y){ return QWidget::event(ev); } return true; } else{ return QWidget::event(ev); //其它事件按原有方式處理 //注意這里一定要把不特殊處理的按原有方式處理 } }
除了在event()向每個具體的類對象分發事件以前,我們可以用event()進行事件的提前處理,而到達底層具體事件處理時,先查看event()對事件的返回值的狀態(true/false),再確定處理方式。而在event()之上,我們也可以通過eventFilter()來完成事件的過濾(提前處理),因為這(eventFilter())就是事件過濾器。說到底無論是事件分發還是事件過濾,都是對事件的提前處理,並向底層返回一個處理狀態。只是如果多個類中要對同一事件進行相同的提前處理,需要再多個類中實現多次event()的覆寫,eventFilter()則需要一次即可。下例中是對label標簽頁的事件提前處理/事件過濾:
//ui->label->installEventFilter(this);//在構造函數中需要安裝過濾器,這是不同於event的一個地方 //對於不同的類要分別安裝過濾器 bool MyWidget::eventFilter(QObject *obj, QEvent *ev) { //對標簽label進行事件過濾 if(obj == ui->label){ QMouseEvent * env = static_cast<QMouseEvent *>(ev); if(ev->type() == QEvent::MouseMove){ ui->label->setText(QString("<center><h1>Mouse Move:(%1, %2)" "</h1></center>").arg(env->x()).arg(env->y())); return true; //結束傳播 } else if(ev->type() == QEvent::MouseButtonPress){ ui->label->setText(QString("<center><h1>Mouse pressed</h1></center>")); return true; //結束傳播 } else if(ev->type() == QEvent::MouseButtonRelease){ ui->label->setText(QString("<center><h1>Mouse released</h1></center>")); return true; //結束傳播 } } //其余的事件按照默認(未覆寫)的處理方式處理 return QWidget::eventFilter(obj,ev); }
注意:event()、eventFilter()雖然可以用來屏蔽某些事件,但是我們一般不會去修改這兩個函數,而是直接在具體的事件處理函數中進行處理操作。