引言
個人認為,事件機制是Qt最難以理解且最為精妙的一部分。事件主要分為兩種:
- 在與用戶交互時發生。比如按下鼠標(mousePressEvent),敲擊鍵盤(keyPressEvent)等。
- 系統自動發生,比如計時器事件(timerEvent)等。
在發生事件時(比如說上面說的按下鼠標),就會產生一個QEvent對象(這里是QMouseEvent,為QEvent的子類),這個QEvent對象會傳給當前組件的event函數。如果當前組件沒有安裝事件過濾器(這個后面會提到),則會被event函數發放到相應的xxxEvent函數中(這里是mousePressEvent函數)。
需要區分的是:事件與信號並不相同。
比如:鼠標單擊按鈕,鼠標事件(QMouseEvent),而按鈕本身發射clicked()信號。一般而言我們只需要關注單擊信號,不用考慮鼠標事件。但是當我們要對該按鈕做額外操作,不想通過信號處理,此時事件就是一個很好的選擇。關閉事件(QCloseEvent)是一個常用的事件。
一,事件

處理事件有5種常用的方法:
- 重新實現部件的paintEvent()、mousePressEvent()等事件處理函數。這是最常用的一種方法,不過只能用來處理特定部件的特定事件(即需要新建類去實現)
- 重新實現notify()函數。這個函數的功能強大,提供了完全的控制,可以再事件過濾器得到事件之間就獲得他們。但是,它一次只能處理一個事件。
- 向QApplication對象上安裝事件過濾器。因為一個程序只有一個QApplication對象,實現的功能和notify()函數相同,優點是可以同時處理多個事件。
- 重新實現event()函數。QObject類的event()函數可以在事件達到默認事件處理函數之前獲得該事件。
- 在對象上安裝事件過濾器。使用事件過濾器可以再一個界面類中同時處理不同子部件的事件(在本類中實現)
實際編程中最常用的是方法(1),其次是方法(5)。方法2要繼承QApplication類,方法3需要全局的事件過濾器,減緩事件的傳遞。
鼠標事件:
常用的鼠標事件:(本篇處理事件用的是方法一:重寫鼠標事件)
- void mousePressEvent(QMouseEvent *event); //單擊
- void mouseReleaseEvent(QMouseEvent *event); //釋放
- void mouseDoubleClickEvent(QMouseEvent *event); //雙擊
- void mouseMoveEvent(QMouseEvent *event); //移動
- void wheelEvent(QWheelEvent *event); //滑輪
鼠標事件使用的時候,加頭文件: #include <QMouseEvent>
重寫事件框架:
1️⃣鼠標按下事件
void Widget::mousePressEvent(QMouseEvent *event) { // 如果是鼠標左鍵按下 if(event->button() == Qt::LeftButton){ ··· } // 如果是鼠標右鍵按下 else if(event->button() == Qt::RightButton){ ··· } }
2️⃣鼠標移動事件
void Widget::mouseMoveEvent(QMouseEvent *event) { // 這里必須使用buttons() if(event->buttons() & Qt::LeftButton){ //進行的按位與 ··· } }
默認情況下,觸發事件需要點擊一下,才能觸發。可設置為自動觸發:setMouseTracking(true);
3️⃣鼠標釋放事件
void Widget::mouseReleaseEvent(QMouseEvent *event) { ··· }
4️⃣鼠標雙擊事件
void Widget::mouseDoubleClickEvent(QMouseEvent *event) { // 如果是鼠標左鍵按下 if(event->button() == Qt::LeftButton){ ··· } }
5️⃣滾輪事件
void Widget::wheelEvent(QWheelEvent *event) { // 當滾輪遠離使用者時 if(event->delta() > 0){ ··· }else{//當滾輪向使用者方向旋轉時 ··· } }
實例演示(在label控件中,移動鼠標獲取實時位置,並顯示在界面上)
- 創建mylabel類,基類設置為QLabel
這里用了類似自定義控件的方法,對Mylabel類進行封裝。設置基類QLabel 是為了在ui界面中提升label控件(即將label控件和Mylabel關聯,提升時候必須二者基類相同)
- 在mylabel.h中聲明鼠標事件
#pragma once #include <qlabel.h> class mylabel : public QLabel { public: mylabel(QWidget* parent = 0); ~mylabel(); public: //鼠標移動事件 void mouseMoveEvent(QMouseEvent* event); //鼠標按下事件 void mousePressEvent(QMouseEvent* event); //鼠標釋放事件 void mouseReleaseEvent(QMouseEvent* event); };
- 在mylabel.cpp中重寫事件
#include "mylabel.h" #include"QMouseEvent" mylabel::mylabel(QWidget* parent) :QLabel(parent) { } mylabel::~mylabel() { } //鼠標移動顯示坐標 void mylabel::mouseMoveEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) //進行的按位與(只有左鍵點擊移動才滿足) { QString str = QString("Move:(X:%1,Y:%2)").arg(event->x()).arg(event->y()); this->setText(str); } } //鼠標按下顯示“ok,mouse is press” void mylabel::mousePressEvent(QMouseEvent* event) { setText("Ok, mouse is press"); } //鼠標釋放清除顯示 void mylabel::mouseReleaseEvent(QMouseEvent* event) { setText(" "); }
- 在主函數(QTest.cpp)中聲明mylabel的類對象(即聲明一個mylabel類的label控件)
#include "qtest.h" QTest::QTest(QWidget *parent) : QWidget(parent) { ui.setupUi(this);
//聲明mylabel類的控件 mylabel* label1 = new mylabel(this); label1->setGeometry(QRect(130, 100, 271, 161));
//設置邊框 label1->setFrameShape(QFrame::Panel); }
另外,當調用setMouseTracking(true);時(即設置鼠標狀態為自動觸發),需要將鼠標移動事件的if語句去掉(因為不需要點擊觸發了)
修改maylabel.cpp事件:
#include "mylabel.h" #include"QMouseEvent" mylabel::mylabel(QWidget* parent) :QLabel(parent) { //設置鼠標狀態(自動觸發) setMouseTracking(true); } mylabel::~mylabel() { } //鼠標移動顯示坐標 void mylabel::mouseMoveEvent(QMouseEvent* event) { QString str = QString("Move:(X:%1,Y:%2)").arg(event->x()).arg(event->y()); this->setText(str); } //鼠標按下顯示“ok,mouse is press” void mylabel::mousePressEvent(QMouseEvent* event) { setText("Ok, mouse is press"); } //鼠標釋放清除顯示 void mylabel::mouseReleaseEvent(QMouseEvent* event) { setText(" "); }
效果展示:
😒這里用的是代碼創建label控件,那么能不能用ui界面編輯然后在對label控件提升呢?
答案是可以的,但是需要注意的是:此處不能選擇全局包含
否則會出現:
我想其中的原因主要是因為:
本實例是新建了一個mylabel類,而不是像QT常用控件(三)——自定義控件封裝 - 唯有自己強大 - 博客園 (cnblogs.com)這篇博文中直接新添加了一個設計師界面類(即包含ui .h .cpp)。當選擇全局包含時,就包含了主類。
其實也有解決的辦法:需要在提升界面的頭文件處,將工程目錄下自定義控件的地址放於此處(本篇地址:C:/Users/WFD/Desktop/QTest/QTest/mylabel.h)
二,事件的分發:event函數
上面提到的xxxEvent函數,稱為事件處理器(event handler)。而event函數的作用就在於事件的分發。如果想在事件的分發之前就進行一些操作,比如監聽(阻塞)鼠標按下事件。
如果希望在事件分發之前做一些操作,就可以重寫這個 event()函數了。比如我們希望阻塞鼠標按下事件,那么我們就在新建的Mylabel類中重寫event()函數(該類的父類是QLabel)
- 在Mylabel.h中聲明event事件
#include"qlabel.h" class Mylabel : public QLabel { public: explicit Mylabel(QWidget* parent = 0); //鼠標按下事件 void mousePressEvent(QMouseEvent* event); //鼠標釋放事件 void mouseReleaseEvent(QMouseEvent* event); //聲明event事件 bool event(QEvent* e); };
- 在Mylabel.cpp中重寫event事件。
#include "Mylabel.h" #include"QMouseEvent" Mylabel::Mylabel(QWidget* parent) :QLabel(parent) { } //重寫鼠標按下事件 void Mylabel::mousePressEvent(QMouseEvent* event) { this->setText(QString("mouse is press x:%1,y:%2").arg(event->x()).arg(event->y())); } //重寫鼠標釋放事件 void Mylabel::mouseReleaseEvent(QMouseEvent* event) { this->setText("mouse is release "); } //重寫event事件 bool Mylabel::event(QEvent* e) { //如果鼠標按下,再事件分發中做攔截 if (e->type()==QEvent::MouseButtonPress) {
//靜態轉換(將QEvent的對象轉換為QMouseEvent對象) QMouseEvent* event = static_cast<QMouseEvent*>(e); this->setText(QString("event mouse is press x:%1,y:%2").arg(event->x()).arg(event->y())); return true;//返回ture,說明用戶自己處理事件,不往下分發(即攔截上面的按下事件) } return QLabel::event(e); }
點擊鼠標可以看到,觸發的是event的事件(即阻塞了mousePressEvent的事件)。特別需要注意的是:在將不需要阻塞分發的時候,需要分發給父類的event函數處理。即(return QLable::event(e);)
三,事件過濾器(Even Filter)
某些應用場景下,需要攔截某個組件發生的事件,讓這個事件不再向其他組件進行傳播,這時候可以為這個組件或其父組件安裝一個事件過濾器,該過濾器在event分發之前進行攔截。
事件的過濾有兩個步驟:
1️⃣對QObject組件安裝過濾器(調用installEvenFilter函數)
void QObject::installEventFilter ( QObject * filterObj );
參數filterobj 是指誰為組件安裝過濾器(一般是父類)
- 這個函數接受一個 QObject *類型的參數。記得剛剛我們說的,eventFilter()函數是 QObject 的一個成員函數,因此,任意 QObject 都可以作為事件過濾器(問題在於,如果你沒有重寫 eventFilter()函數,這個事件過濾器是沒有任何作用的,因為默認什么都不會過濾)。已經存在的過濾器則可以通過QObject::removeEventFilter()函數移除。
-
我們可以向一個對象上面安裝多個事件處理器 ,只要調用多次installEventFilter()函數。如果一個對象存在多個事件過濾器,那么,最后一個安裝的會第一個執行,也就是后進先執行的順序。
2️⃣事件過濾器的重寫(evenFilter函數)
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
可以看到,函數有兩個參數,一個為具體發生事件的組件,一個為發生的事件(產生的QEvent對象)。當事件是我們感興趣的類型,可以就地進行處理,並令其不再轉發給其他組件。函數的返回值也是bool類型,作用跟even函數類似,返回true為不再轉發,false則讓其繼續被處理。
#include "qtest.h" #include"qmouseevent" QTest::QTest(QWidget *parent) : QWidget(parent) { ui.setupUi(this); //第一步:給label添加過濾器 ui.label->installEventFilter(this); } //第二步:重寫過濾事件 bool QTest::eventFilter(QObject* obj, QEvent* e) { if (obj == ui.label) { //如果鼠標按下,再事件分發中做攔截 if (e->type() == QEvent::MouseButtonPress) { QMouseEvent* event = static_cast<QMouseEvent*>(e); ui.label->setText(QString("eventfilter mouse is press x:%1,y:%2").arg(event->x()).arg(event->y())); return true;//返回ture,說明用戶自己處理事件,不往下分發(即攔截上面的按下事件) } } return QWidget::eventFilter(obj, e); } //重寫鼠標按下事件 void QTest::mousePressEvent(QMouseEvent* event) { ui.label->setText(QString("mouse is press x:%1,y:%2").arg(event->x()).arg(event->y())); } //重寫事件分發 bool QTest::event(QEvent* e) { //如果鼠標按下,再事件分發中做攔截 if (e->type() == QEvent::MouseButtonPress) { QMouseEvent* event = static_cast<QMouseEvent*>(e); ui.label->setText(QString("event mouse is press x:%1,y:%2").arg(event->x()).arg(event->y())); return true;//返回ture,說明用戶自己處理事件,不往下分發(即攔截上面的按下事件) } return QWidget::event(e); }
運行結果:
可以看到在過濾器事件中就監聽了鼠標按壓(即阻塞了后面的事件分發和鼠標按壓)