事件處理
- 一. 事件
- 事件處理
- 鼠標事件處理
- 按鍵事件處理
- 定時器事件處理
- 窗口事件處理
- 二. 事件分發器
- 三. 事件過濾器
雖然 Qt 是跨平臺的 C++ 開發框架,Qt 的很多能力其實是操作系統提供的,只不過 Qt 封裝了系統 API,程序是運行在操作系統上的,需要系統給我們提供支撐。如:事件、文件操作、多線程編程、網絡編程、多媒體。
一. 事件
- 信號槽:用戶進行的各種操作,就可能會產生出信號,可以給某個信號指定槽函數,當信號觸發的時候,就能夠自動的執行到對應的槽函數。
- 事件:用戶進行的各種操作,也會產生事件,程序員同樣可以給事件關聯上處理函數 (處理的邏輯),當事件觸發的時候,就能夠執行到對應的代碼。
- 事件本身是操作系統提供的機制,Qt 也同樣把操作系統事件機制進行了封裝,拿到了 Qt 中,但是由于事件對應的代碼編寫起來不是很方便。
- Qt 對于事件機制又進一步的封裝,就得到了信號槽,信號槽就是對于事件的進一步封裝,事件是信號槽的低層機制。
- 實際 Qt 開發程序的過程中,絕大部分和用戶之間進行的交互都是通過 “信號槽” 來完成的,有些特殊情況下,信號槽不一定能搞定 (某個用戶的動作行為,Qt 沒有提供對應的信號),此時就需要通過重寫事件處理函數,來手動處理事件的響應邏輯。
- 開發事件機制給我們程序員,我們就可以根據實際的需要進行更深度的定制化 DIY (自己動手做) 操作了。
- 用戶進行了很多操作,就會產生很多的事件,當然也會產生出很多的信號。
Qt 中使用個對象來表示一個事件,所有的 Qt 事件均繼承抽象類 QEvent。
- 事件是由系統或者 Qt 平臺本身在不同的時刻發出的,當用戶按鼠標、敲下鍵盤,或者是窗口需要重新繪制的時候,都會發出一個相應的事件。
- 一些事件是在用戶操作時發出,如鍵盤事件、鼠標事件等,另一些事件則是由系統本身自動發出,如定時器事件。
常見的 Qt 事件如下:
常見事件描述:
事件處理
事件的處理:讓一段代碼和某個事件關聯起來,當事件觸發的時候,就能夠執行這段代碼。
- 之前信號槽這里通過 connect 來完成上述關聯的。
- 對于事件來說不一樣,讓當前的類,重寫某個事件處理函數,這里用到的是 “多態” 機制,創建子類,繼承 Qt 已有的類,在子類中重寫父類的事件處理函數,后續事件觸發的過程中,就會通過多態這樣的機制,執行到我們自己寫的子類的函數。
- 事件處理一般常用的方法為:重寫相關的 Event 函數。在 Qt 中,幾乎所有的 Event 函數都是虛函數,所以可以重新實現。如:在實現鼠標的進入和離開事件時,直接重新實現 enterEvent() 和 leaveEvent() 即可。
enterEvent() 和 leaveEvent() 函數原型如下:
代碼:鼠標進入和離開控件
給 ui 文件設置 QLabel,并添加上邊框,為了方便觀察當前鼠標是否進入和離開,如下:
這里需要創建 QLabel 的子類,重寫 enterEvent 和 leaveEvent,如下:
要想重寫父類的函數,就需要確保你這邊寫的函數名字和函數的參數列表都完全一致 (形參名無所謂),謹防單詞拼寫錯,正常來說 Qt Create 應該要能夠提示出來的,但實際上沒有提示。
運行代碼后,發現鼠標進入或者離開 QLabel 時,應用程序輸出中并沒有打印出 enterEvent 和 leaveEvent,如下:
原因分析:我們在 ui 文件中使用的控件是 QLabel,而 QLabel 默認不處理 enterEvent 和 leaveEvent,而且要啟用鼠標跟蹤功能也需要額外的操作。
我們需要在 ui 文件中,使用自己定義的 Label,如下:
通過 “提升為” 這樣的方式,就可以把 Qt Designed 中拖上去的控件的類型轉換為自定義的控件類型,如下:
運行程序后,此時就說明當前的 enterEvent 和 leaveEvent 這兩個事件就被我們給捕捉到了,如下:
鼠標事件處理
在 Qt 中,鼠標事件是用 QMouseEvent 類來實現的。
- 當在窗口中 “按下鼠標” 或者 “移動鼠標” 時,都會產生鼠標事件。
- 利用 QMouseEvent 類可以獲取鼠標的哪個鍵被按下了以及鼠標的當前位置等信息。
- 在 Qt 幫助文檔中,可以查找 QMouseEvent 類,如下:
代碼:鼠標點擊事件
在 Qt 中,鼠標按下時,通過執行虛函數 QWidget::mousePressEvent(QMouseEvent *event) 來捕獲的。而其中鼠標可以通過 “左鍵”、“右鍵”、“滾輪” 等等按下,如何區分?通過 event->button() 函數的返回值區分。
- 鼠標左鍵:Qt::LeftButton
- 鼠標左鍵:Qt::RightButton
- 鼠標滾輪:Qt::MidButton
同理將 QLabel 提升為我們自定義的 Label,如下:
修改 label.h 和 label.cpp 文件,如下:
代碼:鼠標釋放事件
鼠標釋放事件是通過虛函數 QWidget::mouseReleaseEvent(QMouseEvent *event) 來捕獲的。
代碼:鼠標雙擊事件
鼠標雙擊事件是通過虛函數 QWidget::mouseDoubleClickEvent(QMouseEvent *event) 來實現的。
注意:這里第二次按下的時候,才能夠識別時 “雙擊”,比如:程序可能有一些單擊邏輯、有另一些雙擊邏輯,如果我們沒注意到,可能雙擊操作就會觸發單擊的邏輯,可能就有 bug
代碼:鼠標移動事件
- 鼠標移動事件是通過虛函數 QWidget::mouseMoveEvent(QMouseEvent *event) 來實現的。
- 同時為了實時捕獲鼠標位置信息,需要通過函數 setMouseTracking(bool enable) 來追蹤鼠標的位置。
- 說明:setMouseTracking() 函數默認是 false,需要設置為 true,才能實時捕獲鼠標位置信息。否則只有當鼠標按下時才能捕獲其位置信息。
剛才重寫鼠標事件的操作,都是在自定義的 Label 中完成的,此時鼠標只有在 Label 范圍內進行動作的時候,才能捕獲到,也可以把這些操作直接放到 Widget (QWidget 的子類) 來完成,這樣的話,鼠標在整個窗口中進行的各種動作都能獲取到了。如下:
注意:
- 鼠標移動不同于鼠標按下,隨便移動一下鼠標,就會產生大量的鼠標移動事件,當你進行捕獲事件的時候,久其是在這里再進行一些復雜邏輯的時候,程序負擔就很重,很容易產生卡頓之類的情況。
- Qt 為了保證程序的流暢性,默認情況下不會對鼠標移動進行追蹤,鼠標移動的時候不會調用 mouseMoveEvent(),除非顯示告訴 Qt 就要追蹤鼠標位置。
如下:
代碼:鼠標滾輪事件
在 Qt 中,鼠標滾輪事件是通過 QWindow::wheelEvent(QWheelEvent *event) 函數來實現的。滾輪滑動的距離可以通過 event->delta() 函數獲取。
- 返回值:滾輪滑動的距離,正數表示滾輪相對于用戶向前滑動,負數表示滾輪相對于用戶向后滑動。
可以通過滾輪可以實現縮放字體的大小!
按鍵事件處理
- 要想獲取到用戶的鍵盤按鍵,可以通過 QShortCut,這是信號槽機制封裝過,獲取鍵盤按鍵的方式,站在更底層的角度,也可以通過事件獲取到當前用戶鍵盤按鍵按下的情況。
- Qt 中的按鍵事件是通過 QKeyEvent 類來實現的。當鍵盤上的按鍵被按下或者被釋放時,鍵盤事件便會觸發。在幫助文檔中查找 QKeyEvent 類如下:
代碼:按下單個按鍵
按下單個按鍵是通過虛函數 QWidget::keyPressEvent(QKeyEvent *event) 來實現的,搭配 event->key() 函數,來獲取哪個按鍵被按下,如下:
代碼:按下組合按鍵
在 Qt 助手中搜索:Qt::KeyboardModifier,如下圖示:
Qt::KeyboardModifier 中定義了在處理鍵盤事件時對應的修改鍵。在 Qt 中,鍵盤事件可以與修改鍵一起使用,以實現一些復雜的交互操作。KeyboardModifier 中修改鍵的具體描述如下:
通過 event->modifiers() 函數,判斷按下了哪一個修改鍵,如下:
定時器事件處理
Qt 中在進行窗口程序的處理過程中,經常要周期性的執行某些操作,或者制作一些動畫效果,使用定時器就可以實現。所謂定時器就是在間隔一定時間后,去執行某一個任務。定時器在很多場景下都會使用到,如彈窗自動關閉之類的功能等。
Qt 中的定時器分為 QTimerEvent 和 QTimer 這兩個類:
- QTimerEvent 類,用來描述一個定時器事件。在使用時需要通過 QObject::startTimer() 函數來開啟一個定時器,這個函數需要輸入一個以毫秒為單位的整數作為參數來表明設定的時間,它返回的整型值代表這個定時器。當定時器溢出時 (即定時時間到達) 就可以在 QObject::timerEvent() 函數中獲取該定時器的編號來進行相關操作。
- QTimer 類,用來實現一個定時器,背后是 QTimerEvent 定時器事件進行支撐的,它提供了更高層次的編程接口,例如:可以使用信號和槽,還可以設置只運行一次的定時器。
代碼:基于 QTimerEvent 定時器,實現倒計時
通過 QObject::startTimer(int msec) 這個虛函數,開啟定時器,返回一個身份標識符,用于標識這個定時器,功能就是每隔一段時間,執行 QObject::timerEvent(QTimerEvent *event) 函數,通過 event->timerId() 函數來獲取定時器標識符,通過 QObject::killTimer(int id) 函數來取消某個定時器,如下:
代碼:基于 QTimer 定時器,實現倒計時
通過創建 QTimer 對象,調用其中的 start(int msec) 函數來啟動定時器,每隔一段時間定時器對象就會發出 QTimer::timeout 信號,此時需要將該信號和處理定時器的槽函數建立連接,通過 stop() 函數來停止定時器,如下:
總結:使用 timerEvent 比 QTimer 還是要更復雜一些,需要手動管理 timerId,還需要區分這次函數調用是哪個 timerId 引起的,后續實際開發中,使用 QTimer 即可。
代碼:獲取系統日期及時間
- 在 Qt 中,獲取系統的日期及實時時間可以通過 QTimer 類 和 QDateTime 類,QDateTime 類提供了字符串格式的時間。
- 字符串形式的時間輸出格式由 toString() 方法中的 format 參數列表決定,可用的參數列表如下:
在 ui 文件中,放置一個 QLabel 控件,用來顯示日期及時間,同時放置兩個按鈕 “開始” 和 “停止”,如下:
窗口事件處理
- 通過 QWidget::moveEvent(QMoveEvent *event) 虛函數,是窗口移動時,觸發的事件。
- 通過 QWidget::resizeEvent(QResizeEvent *event) 虛函數,是窗口大小改變時,觸發的事件。
代碼:窗口移動、窗口大小改變,事件的處理
二. 事件分發器
事件分發器概述:
- 在 Qt 中,事件分發器 (Event Dispatcher) 是一個核心概念,用于處理 GUI 應用程序中的事件。
- 事件分發器負責將事件從一個對象傳遞到另一個對象,直到事件被處理或被取消。
- 每個繼承 QObject 類 或 QObject 類本身,都可以在本類中重寫 bool event(QEvent *event) 函數,來實現相關事件的捕獲和攔截。
事件分發器工作原理:
- 在 Qt 中,我們發送的事件都是傳給了 QObject 對象,更具體點是傳給了 QObject 對象的 event() 函數。
- 所有的事件都會進入到 event() 函數里面,那么我們處理事件就要重寫這個 event() 函數。
- event() 函數本身不會去處理事件,而是根據 事件類型 (type值) 調用不同的事件處理函數。
- 事件分發器就是工作在應用程序向下分發事件的過程中,如下圖:
如上圖,事件分發器用于分發事件。在此過程中,事件分發器也可以做攔截操作。事件分發器主要是通過 bool event(QEvent *event) 函數來實現。其返回值為布爾類型,若為 ture 代表攔截,不向下分
發。
Qt 中的事件是封裝在 QEvent 類中,在 Qt 助手中輸入 QEvent 可以查看其所包括的事件類型,如下圖示:
代碼:鼠標點擊事件 和 事件分發器
事件處理順序:
- 當鼠標按下事件發生時,系統首先調用 event() 方法,判斷事件類型是否為 QEvent::MouseButtonPress:
- 如果是:則返回 true 表示事件已被處理,不會繼續分發給 mousePressEvent()
- 如果不是: event() 返回 QWidget::event(event),表示交給父類處理,結果就是,事件會繼續分發到 mousePressEvent(),處理鼠標按下事件。
三. 事件過濾器
在 Qt 中,一個對象可能經常要查看或攔截另外?個對象的事件,如對話框想要攔截按鍵事件,不讓別的組件接收到,或者修改按鍵的默認值等。通過上面,已經知道,Qt 創建了 QEvent 事件對象之后,會調用 QObject::event() 函數來處理事件的分發。
顯然,我們可以在 event() 函數中實現攔截的操作,由于 event() 函數是 protected 的,因此需要繼承已有類。如果組件很多,就需要重寫很多個 event() 函數。這當然相當麻煩,更不用說重寫 event() 函數還得小心一堆問題。好在 Qt 提供了另外一種機制來達到這一目的:事件過濾器。
事件過濾器是在應用程序分發到 event 事件分發器之前,再做一次更高級的攔截,如下圖示:
事件過濾器的一般使用步驟:安裝事件過濾器、重寫事件過濾器函數 eventfilter()
void QObject::installEventFilter(QObject *filterObj)