Qt事件
除了信號和槽通信機制外,Qt中還提供了事件處理機制實現與用戶的交互和對象間的通信。Qt捕獲底層操作系統消息,進行封裝之后轉換為Qt事件,事件處理后才發出信號。
一、事件概述
Qt中事件是程序內部或外部發生的動作。比如程序外部:用戶移動鼠標、單擊鼠標、鼠標拖拽、按下按鍵等操作都會產生對應的事件。程序內部:窗口第一次顯示、放大、縮小等。
📌 兩類事件
-
外部事件(用戶操作導致的)
就是人跟程序互動時觸發的,比如:- 你 移動鼠標 → 程序收到一個“鼠標移動事件”。
- 你 單擊鼠標 → 程序收到一個“鼠標點擊事件”。
- 你 按下鍵盤 → 程序收到一個“鍵盤按下事件”。
- 你 拖動窗口 → 程序收到“鼠標拖拽事件”。
👉 就好比你在敲門、按電梯按鈕,程序必須做出反應。
-
內部事件(程序自己觸發的)
就算你啥都不點,程序自己也會產生一些動作,比如:- 窗口 第一次顯示 → 產生“窗口顯示事件”。
- 窗口被 放大/縮小 → 產生“窗口大小改變事件”。
- 程序定時器到點了 → 產生“定時器事件”。
👉 就像手機自己亮屏、鎖屏,這是系統自己觸發的。
1、事件來源
事件有兩個來源:程序外部和程序內部
- 在與用戶交互時產生,比如用戶點擊鼠標、按下按鍵等,此時操作系統會感知到用戶的行為,并產生消息,然后將消息投遞到應用程序的消息隊列當中;應用程序從消息隊列中提取消息,并將其轉化為Qt事件,生產事件對象。
- 由Qt應用程序自身產生。例如當窗口第一次顯示時,會產生一個繪制事件,以通知窗口需要重新繪制自身,從而使窗口可見。這是由程序內部產生的事件。
2、Qt事件處理機制
(1)當操作系統發生一個事件時,事件首先會被操作系統內核中的設備驅動程序所感知,然后發送給操作系統的事件管理系統,事件管理系統將其放入到事件隊列中。
(2)Qt應用程序作為一個客戶端,通過調用QApplication的exec()
函數啟動事件循環,這個循環會不斷地從事件隊列取出事件,Qt捕獲之后,會將該事件轉換為相應的Qt事件對象。
(3)事件自己不能處理自己,循環中依次取出事件首先交給notify()函數,通過notify()函數派發給處理事件的對象。由QObject類以及其派生類對象進行事件的處理,通過重寫event()
函數,或重寫對應的事件處理函數完成事件的處理。
完整流程總結
- 程序運行后,
QApplication::exec()
啟動事件循環,持續監聽事件; - 事件產生后,先到
QApplication::notify()
做全局處理; - 再傳遞到目標
QObject
的event()
方法,識別事件類型; - 最終調用具體的
xxxEvent()
函數,執行實際邏輯。
3、事件與信號
Qt的事件與信號很容易混淆。
事件是底層操作系統所產生的消息,由Qt捕獲之后,封裝為對應的事件對象,比如鼠標單擊對應的事件類型是 QEvent::MouseButtonPress.我們在程序中可以通過重寫對應的事件處理函數或event()函數進行事件的處理。
Qt為了方便事件的處理,引入了信號(Signal)的概念,封裝了一些事件操作的標準預處理,比如對QPushButton預定義了clicked()
信號。使用戶不必去處理底層事件,只需要處理信號即可。當一個事件觸發后,對象通過發射一個信號(Signal)進行通知,而其他對象的槽函數(Slot)可以連接到這個信號,從而實現對事件的處理。
信號是對事件的封裝。
二、事件類型
常見的事件類型包括:
- 鍵盤事件(如QKeyEvent,當用戶按下或釋放鍵盤按鍵時產生)
- 鼠標事件(如QMouseEvent,包含鼠標點擊、移動、滾輪滾動等操作)
- 窗口事件(如resize、paint、move等,與窗口大小、位置、重繪相關的事件)
Qt捕獲之后,會將該事件轉換為相應的Qt事件對象,所有事件都是QEvent類或其派生類的實例,常見的事件類如下:
類名 | 作用 |
---|---|
QMouseEvent | 鼠標事件 |
QWheelEvent | 滾輪事件 |
QKeyEvent | 鍵盤事件 |
QPaintEvent | 繪畫事件 |
QTimerEvent | 定時器事件 |
QResizeEvent | 窗口大小改變事件 |
QCloseEvent | 關閉事件 |
QShowEvent | 顯示事件 |
QHideEvent | 隱藏事件 |
三、鼠標事件
用戶點擊鼠標 → 硬件信號 → 操作系統事件 → Qt 事件循環接收 → 轉換為 QMouseEvent → 傳遞給按鈕 → 按鈕判斷有效后發出 clicked() 信號 → 連接的槽函數執行
鼠標事件涉及鼠標左鍵或右鍵按下,釋放、雙擊、移動等操作。QMouseEvent類用于處理鼠標事件。
事件處理函數都是虛函數,全部聲明在QWidget類中,如果要在子類中處理事件,需要重寫對應的事件處理函數。
與鼠標事件相關的處理函數如下:
事件處理函數 | 參數類型 | 描述 |
---|---|---|
mousePressEvent() | QMouseEvent | 鼠標按鍵按下 |
mouseDoubleClickEvent() | QMouseEvent | 鼠標雙擊 |
mouseReleaseEvent() | QMouseEvent | 鼠標釋放 |
mouseMoveEvent() | QMouseEvent | 鼠標移動 |
wheelEvent() | QWheelEvent | 鼠標滾輪滾動 |
1、重寫鼠標事件函數
創建QWidget類的子類,重寫事件處理函數
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget> // QWidget 是所有界面部件的基類
#include <QPoint> // QPoint 用于存儲點的坐標namespace Ui {
class Widget; // 前向聲明 Qt Designer 生成的 Ui::Widget 類
}class Widget : public QWidget
{Q_OBJECT // Qt 的宏,啟用信號與槽機制public:explicit Widget(QWidget *parent = nullptr); // 構造函數~Widget(); // 析構函數protected:// 重寫鼠標按下事件函數,用于獲取鼠標點擊位置void mousePressEvent(QMouseEvent *e) override;// 重寫鼠標滾輪事件函數,用于實現圖標放大/縮小void wheelEvent(QWheelEvent *e) override;// 重寫鼠標移動事件函數,用于實現窗口拖動void mouseMoveEvent(QMouseEvent *event) override;private:Ui::Widget *ui; // UI 界面指針QPoint offset; // 鼠標相對于窗口左上角的偏移量(用于拖動窗口)
};#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMouseEvent> // 鼠標事件類
#include <QDebug> // 調試輸出// 構造函數
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this); // 初始化 UIui->label->setPixmap(QPixmap(":/images/iot.png")); // 在 label 上顯示圖片ui->label->setScaledContents(true); // 圖片隨 label 大小自動縮放setWindowFlags(Qt::FramelessWindowHint | windowFlags()); // 去掉窗口邊框(自繪窗口)
}// 析構函數
Widget::~Widget()
{delete ui; // 釋放 UI 內存
}// 鼠標按下事件
void Widget::mousePressEvent(QMouseEvent *event)
{if (event->type() == QEvent::MouseButtonPress) // 判斷是否是鼠標按下事件{if (event->button() == Qt::LeftButton) // 如果是鼠標左鍵{qDebug() << "鼠標左鍵被按下,位于用戶區坐標:" << event->pos()<< ",屏幕坐標:" << event->globalPos();offset = event->pos(); // 記錄鼠標按下時,鼠標相對于窗口的位置(用于拖動窗口)}else // 如果是鼠標右鍵{qDebug() << "鼠標右鍵被按下,位于用戶區坐標:" << event->pos();}}
}// 鼠標滾輪事件
void Widget::wheelEvent(QWheelEvent *e)
{// angleDelta().y() > 0 表示向上滾動,< 0 表示向下滾動if (e->angleDelta().y() > 0) // 滾輪向上,放大圖片{ui->label->resize(ui->label->width()+5, ui->label->height()+5);}else // 滾輪向下,縮小圖片{ui->label->resize(ui->label->width()-5, ui->label->height()-5);}
}// 鼠標移動事件
void Widget::mouseMoveEvent(QMouseEvent *event)
{if (event->buttons() & Qt::LeftButton) // 當鼠標左鍵按下并移動時{// 移動窗口位置// globalPos() 是鼠標在屏幕上的坐標// offset 是鼠標相對窗口的偏移量// 兩者相減得到窗口新的左上角位置this->move(event->globalPos() - offset);}
}
- mousePressEvent(QMouseEvent *e)
👉 處理鼠標按下事件。
- 左鍵:記錄點擊位置(用于窗口拖動)。
- 右鍵:只是輸出調試信息。
- wheelEvent(QWheelEvent *e)
👉 處理鼠標滾輪事件。
- 向上滾:放大圖片。
- 向下滾:縮小圖片。
- mouseMoveEvent(QMouseEvent *event)
👉 處理鼠標移動事件(拖動窗口)。
- 鼠標按住左鍵拖動時,計算窗口新位置,并移動窗口。
2、QMouseEvent對象
QMouseEvent封裝鼠標事件的類,常用的成員函數:
成員函數 | 描述 |
---|---|
Qt::MouseButton button() const; | 返回產生事件的按鈕 |
QPoint globalPos() const; | 返回鼠標的位置,使用屏幕坐標 |
QPoint pos() const; | 返回鼠標的位置,使用用戶區坐標 |
QEvent::Type type() const | 返回事件類別,如按下、釋放或雙擊等 |
3、QWheelEvent對象
QWheelEvent類封裝了鼠標滾輪事件,如滾輪滾動的方向、幅度、位置等。
常用的成員函數:
成員函數 | 描述 |
---|---|
QPoint pos() const; | 返回事件發生時鼠標的位置(用戶區坐標) |
QPoint globalPos() const; | 返回事件發生時鼠標的位置(屏幕坐標) |
QPoint angleDelta() const; | 返回滾輪的滾動量 |
在 Qt 里,事件函數(event handler) 是 QWidget
或其他控件類自帶的虛函數,比如:
mousePressEvent(QMouseEvent *e)
—— 鼠標按下wheelEvent(QWheelEvent *e)
—— 鼠標滾輪keyPressEvent(QKeyEvent *e)
—— 鍵盤按下paintEvent(QPaintEvent *e)
—— 繪制事件
🔹 為什么要 重寫(override) 這些函數?
- Qt 已經幫我們定義了這些事件函數,但默認實現是“什么都不做”
- 比如
mousePressEvent()
默認不會幫你輸出坐標,也不會幫你移動窗口。 - 如果你需要自定義行為,就要 重寫 它們,寫上自己的邏輯。
- 比如
- 讓控件有“特殊功能”
- 例子:重寫
mousePressEvent
,實現點擊后窗口能拖動。 - 如果不重寫,窗口只能被系統默認拖動(有標題欄時),去掉邊框后根本沒法拖動。
- 例子:重寫
- 事件驅動編程的核心
- Qt 是 事件驅動 的:鼠標、鍵盤、窗口變化都會產生事件。
- 程序如何響應這些事件,就要靠重寫事件函數。
🔹 舉個通俗例子:
把 事件函數 想象成「門鈴」。
- Qt 框架:默認幫你裝好一個門鈴(函數)。
- 系統按門鈴(鼠標點擊 / 滾輪滾動 / 按鍵),就會觸發事件。
- 如果你沒寫(不重寫),按門鈴沒人理(默認什么都不做)。
- 如果你寫了(重寫),就能定義:
- 門鈴響時開燈
- 門鈴響時開門
- 門鈴響時發個消息
? 所以:
重寫事件函數 = 告訴程序當某個事件發生時,你要如何反應。
四、鍵盤事件
1、鍵盤事件處理函數
事件處理函數 | 對應事件類型 | 參數類型 | 描述 |
---|---|---|---|
void keyPressEvent(QKeyEvent *event); | QEvent::KeyPress | QKeyEvent | 按鍵按下 |
void keyReleaseEvent(QKeyEvent *event); | QEvent::KeyRelease | QKeyEvent | 按鍵釋放 |
下面是處理鍵盤事件的示例:
通過按鍵移動圖片,重寫鍵盤按下事件的處理函數。
#ifndef KEYEVENTWIDGET_H // 頭文件防衛,防止重復包含
#define KEYEVENTWIDGET_H#include <QWidget> // QWidget 是所有可視化窗口控件的基類namespace Ui {
class KeyEventWidget; // 聲明一個 UI 命名空間里的 KeyEventWidget 類(Qt Designer 生成)
}class KeyEventWidget : public QWidget // 自定義類,繼承 QWidget
{Q_OBJECT // Qt 元對象系統宏,支持信號槽機制等public:explicit KeyEventWidget(QWidget *parent = nullptr); // 構造函數,支持父子對象機制~KeyEvevtWidget(); // 析構函數,釋放資源(這里注意拼寫錯了,應該是 KeyEventWidget)protected:// 處理按鍵事件void keyPressEvent(QKeyEvent *e); // 重寫 QWidget 的鍵盤按下事件函數private:Ui::KeyEventWidget *ui; // 指向 UI 界面類的指針,Qt Designer 自動生成的 UI 操作接口
};#endif // KEYEVENTWIDGET_H // 文件結尾的防衛符號
繼承 QWidget
KeyEventWidget
是一個自定義窗口類,繼承自 QWidget,可以顯示在界面上。
重寫 keyPressEvent(QKeyEvent *e)
- 這是 Qt 里專門處理鍵盤按下的事件函數。
- 默認實現是“不處理”,所以我們重寫后,可以實現:
- 按下某個鍵時打印信息
- 按下 Esc 鍵關閉窗口
- 按下方向鍵移動控件
KeyEvevtWidget.cpp
#include "KeyEventWidget.h" // 包含自定義的 KeyEventWidget 類頭文件
#include "ui_keyeventwidget.h" // 包含 Qt Designer 生成的 UI 類定義
#include <QWheelEvent> // 包含鼠標滾輪事件類(雖然這里沒用到)
#include <QKeyEvent> // 包含鍵盤事件類KeyEventWidget::KeyEventWidget(QWidget *parent): QWidget(parent) // 調用 QWidget 構造函數,初始化父類, ui(new Ui::KeyEventWidget) // 創建 UI 類的對象
{ui->setupUi(this); // 設置 UI 界面,關聯 .ui 文件中的控件ui->label->setStyleSheet("background-color: #ccc; height: 200px; width:100px"); // 設置 label 的樣式:灰色背景,固定大小(200 高,100 寬)
}KeyEventWidget::~KeyEventWidget() // 析構函數
{delete ui; // 刪除 UI 對象,釋放內存
}void KeyEventWidget::keyPressEvent(QKeyEvent *e) // 重寫按鍵事件處理函數
{// 按上方向鍵 → 標簽向上移動 5 像素if (e->key() == Qt::Key_Up){ui->label->move(ui->label->x(), ui->label->y()-5);}// 按下方向鍵 → 標簽向下移動 5 像素else if (e->key() == Qt::Key_Down){ui->label->move(ui->label->x(), ui->label->y()+5);}// 按左方向鍵 → 標簽向左移動 5 像素else if (e->key() == Qt::Key_Left){ui->label->move(ui->label->x()-5, ui->label->y());}// 按右方向鍵 → 標簽向右移動 5 像素else if (e->key() == Qt::Key_Right){ui->label->move(ui->label->x()+5, ui->label->y());}// 按下 Ctrl + M 組合鍵else if (e->key() == Qt::Key_M && e->modifiers() == Qt::ControlModifier){// 如果窗口是最大化的 → 還原窗口if (windowState() & Qt::WindowMaximized) {setWindowState(windowState() & ~Qt::WindowMaximized);} // 否則 → 最大化窗口else {setWindowState(Qt::WindowMaximized);}}// 其他按鍵 → 使用父類默認處理else{QWidget::keyPressEvent(e);}
}
-
e->key() == Qt::Key_M
判斷用戶按下的是否是 M 鍵。 -
e->modifiers() == Qt::ControlModifier
判斷是否同時按住了 Ctrl 鍵。modifiers()
表示組合鍵(比如 Ctrl、Shift、Alt)。- 這里檢查是否等于
Qt::ControlModifier
,也就是 Ctrl 鍵。
👉 所以整體條件:按下 Ctrl+M。
windowState()
返回窗口的當前狀態,比如:Qt::WindowNoState
→ 普通狀態Qt::WindowMaximized
→ 最大化Qt::WindowMinimized
→ 最小化Qt::WindowFullScreen
→ 全屏
if (windowState() & Qt::WindowMaximized)
&
是按位與運算,檢查當前窗口狀態里是否包含 最大化。- 如果是最大化 → 進入還原操作。
setWindowState(windowState() & ~Qt::WindowMaximized);
~Qt::WindowMaximized
表示“去掉最大化這個標志”。windowState() & ~Qt::WindowMaximized
→ 當前狀態里,去掉“最大化”。- 也就是說:把窗口從最大化還原回普通大小。
else { setWindowState(Qt::WindowMaximized); }
- 如果當前不是最大化 → 就設置成最大化。
Qt 里的 windowState()
返回的值,其實就像 MCU(單片機)里寄存器的值:
- 每一位(或者說每個二進制標志位)代表一個“開關”狀態。
- 如果該位是
1
,說明這個狀態 啟用; - 如果該位是
0
,說明這個狀態 未啟用。
例如:
Qt 定義的窗口狀態(簡化):
狀態宏 | 二進制位 | 說明 |
---|---|---|
Qt::WindowNoState | 0000 | 默認普通狀態 |
Qt::WindowMinimized | 0001 | 最小化 |
Qt::WindowMaximized | 0010 | 最大化 |
Qt::WindowFullScreen | 0100 | 全屏 |
如果 windowState()
返回:
0010
→ 表示窗口是 最大化0001
→ 表示窗口是 最小化0011
→ 表示窗口同時有 最小化 + 最大化(可能組合狀態)
那么:
if (windowState() & Qt::WindowMaximized)
就是在問:
👉 “這個二進制值的 第2位(最大化標志)是不是 1?”
如果是 1,就說明當前窗口已經最大化。
📌 所以它和寄存器檢查位完全一樣。
在嵌入式里,我們常寫:
if (reg & (1<<3)) { ... } // 檢查寄存器的第3位
在 Qt 里就是:
if (windowState() & Qt::WindowMaximized) { ... }
2、QKeyEvent對象
用于描述鍵盤事件,常用成員函數:
成員函數 | 描述 |
---|---|
int key() const; | 獲取按下的鍵 |
int modifiers() const; | 判斷修飾鍵(Ctrl、Shift、Alt)是否在按下狀態 |
五、重寫event()事件函數
event()
函數是QObject類所提供的,特定事件處理函數僅能處理預定義的事件類型,而event()
函數可以處理所有類型的事件。
- 對于只需要處理某種特定類型事件的情況,重寫對應的特定事件處理函數會更加直接。
- 如果需要對多種不同類型的事件進行處理,那么就重寫
event()
函數。
下面是重寫event()實現事件處理示例
- 新建MyWidget類
- mywidget.h文件如下:
#ifndef MYWIDGET_H // 頭文件保護,防止重復包含
#define MYWIDGET_H#include <QWidget> // 引入 QWidget 基類,MyWidget 要繼承它namespace Ui {
class MyWidget; // 前向聲明 UI 界面類(Qt Designer 生成的類)
}class MyWidget : public QWidget // 定義 MyWidget 類,繼承自 QWidget
{Q_OBJECT // Qt 的元對象宏,支持信號槽、事件機制等public:explicit MyWidget(QWidget *parent = nullptr); // 構造函數,父對象默認為空~MyWidget(); // 析構函數,釋放資源// 重寫 Qt 的事件處理函數 event()// 所有事件(鼠標、鍵盤、窗口等)都會先經過 event()// 返回 true 表示事件已處理,不需要再傳遞;false 表示未處理,交給默認處理bool event(QEvent *event);protected:// 自定義的鼠標事件處理函數(自己寫的,名字不是 Qt 內置的)void doPressEvent(QMouseEvent *event);private:Ui::MyWidget *ui; // UI 界面指針,指向 Qt Designer 生成的界面類
};#endif // MYWIDGET_H // 頭文件保護宏結束
event(QEvent *event)
- Qt 的 事件分發函數。
- 凡是傳給這個窗口的事件(鼠標、鍵盤、窗口大小變化等),都會先經過它。
- 如果你重寫它,可以統一攔截和處理不同類型的事件。
doPressEvent(QMouseEvent *event)
- 這是你 自定義的函數,不是 Qt 框架強制要求的。
- 一般用來在
event()
里檢測到鼠標事件后,專門調用它來處理。 - 相當于你自己封裝的“鼠標事件處理邏輯”。
- 返回值
bool
的意義true
→ 表示事件在這里已經被處理完畢,Qt 不會再把它傳遞下去。false
→ 表示你沒處理,Qt 會交給默認的父類邏輯繼續處理。
📌 所以可以理解為:
event()
→ 總入口,像一個“事件分發器”。doPressEvent()
→ 你自己寫的業務邏輯處理函數。
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QEvent>
#include <QMouseEvent>
#include <QDebug>MyWidget::MyWidget(QWidget *parent): QWidget(parent) // 調用父類 QWidget 的構造函數, ui(new Ui::MyWidget) // 創建 UI 界面對象
{ui->setupUi(this); // 初始化 UI 布局和控件
}MyWidget::~MyWidget()
{delete ui; // 釋放 UI 對象,防止內存泄漏
}// 重寫 QWidget 的事件處理函數 event()
bool MyWidget::event(QEvent *event)
{if (event->type() == QEvent::MouseButtonPress) // 判斷是否為鼠標按下事件{// 將接收到的 QEvent 對象強制轉換為 QMouseEvent 對象QMouseEvent *mEvent = dynamic_cast<QMouseEvent*>(event);// 調用自定義處理函數處理鼠標按下事件doPressEvent(mEvent);// 返回 true 表示事件已被處理,不再傳遞給其他處理函數return true;}// 對于其他類型事件,調用基類的 event() 函數進行默認處理return QWidget::event(event);
}// 自定義函數,用于處理鼠標按下事件
void MyWidget::doPressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) // 判斷是否為鼠標左鍵{qDebug() << "左鍵點擊"; // 打印調試信息}else // 其他情況(如右鍵){qDebug() << "右鍵點擊"; // 打印調試信息}
}
-
事件過濾與處理:通過重寫
event()
函數可以攔截所有事件類型,并根據類型選擇性處理。 -
類型轉換:使用
dynamic_cast
將基類QEvent*
轉換為具體事件類型QMouseEvent*
,便于訪問特有屬性。 -
事件消費:返回
true
表示事件已經處理,不會繼續向父類或其他對象傳遞。 -
自定義函數解耦:將具體的鼠標按下處理邏輯放到
doPressEvent()
函數中,使代碼結構更清晰。 -
當 Qt 收到一個事件(比如鼠標、鍵盤、繪圖請求),它會先調用
QWidget::event()
。 -
event()
內部會判斷事件類型,然后再調用更具體的事件處理函數:mousePressEvent()
keyPressEvent()
paintEvent()
- …
所以 如果你重寫了 event()
,但沒有調用 QWidget::event(event)
,相當于“截斷”了事件分發。
?? 重點:Qt 內部的設計就是 依賴 QWidget::event()
去調用其他事件函數。 如果你直接返回 false
,那些具體的事件函數根本不會被調用。
return QWidget::event(event);
是顯式調用 父類版本的 event()
,讓它繼續幫你分發事件。
六、定時器事件
Qt的定時器事件主要用于實現周期性或延遲執行任務的功能。對于實時監控、動畫效果、定期檢查狀態變化以及其他需要按時間順序控制的行為非常有用。
在Qt中,有兩種主要的方式來使用定時器:
1、QTimer類
QTimer
是基于事件循環的定時器,通過創建一個QTimer對象并調用其start()
方法來啟動定時器。當設定的時間間隔到期時,會自動觸發關聯的槽函數(slot)或者發送一個timeout()
信號。- 使用QTimer可以輕松實現在特定時間間隔后執行重復的任務,例如更新用戶界面、輪詢硬件狀態、刷新數據等。
2、定時器事件
(1)對于任意QObject子類,可以調用以下成員函數開啟定時器:
int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer);
該函數會以interval毫秒為周期開啟一個定時器,產生一個QTimerEvent定時器事件,并返回定時器標識。
Qt::TimerType timerType
用于指定計時器的類型:
Qt::VeryCoarseTimer
:這是最不精確的計時器類型,可能有幾秒鐘的誤差。這種類型的計時器會消耗最少的系統資源。Qt::CoarseTimer
:這是一種相對不精確的計時器類型,可能有幾百毫秒的誤差。這種類型的計時器比VeryCoarseTimer
更精確,但仍然消耗較少的系統資源。Qt::PreciseTimer
:這是最精確的計時器類型,誤差通常小于1毫秒。這種類型的計時器提供最高的精度,但可能會消耗更多的系統資源。
(2)QObject子類,可以通過重寫timerEvent()
成員函數處理定時器事件
void timerEvent(QTimerEvent *event);
(3)清除定時器,直到調用QObject類里的成員函數
void killTimer(int id);
下面是一個定時器事件的示例:
timerwidget.h
#ifndef TIMERWIDGET_H
#define TIMERWIDGET_H#include <QWidget>namespace Ui {
class TimerWidget;
}class TimerWidget : public QWidget
{Q_OBJECT
public:explicit TimerWidget(QWidget *parent = nullptr); // 構造函數~TimerWidget(); // 析構函數protected:// 重寫QObject提供的定時器事件處理函數void timerEvent(QTimerEvent *e);private slots:void on_start_clicked(); // “開始”按鈕點擊槽void on_stop_clicked(); // “結束”按鈕點擊槽private:Ui::TimerWidget *ui; // UI指針int timerId; // 保存定時器ID,用于開啟/關閉定時器
};#endif // TIMERWIDGET_H
重寫定時器事件函數,timerwidget.cpp代碼如下:
#include "timerwidget.h"
#include "ui_timerwidget.h"
#include <QTime> // 用于顯示當前時間TimerWidget::TimerWidget(QWidget *parent): QWidget(parent), ui(new Ui::TimerWidget), timerId(-1) // -1表示當前沒有定時器
{ui->setupUi(this); // 創建定時器,周期1秒(1000ms)if (timerId == -1)timerId = startTimer(1000); // startTimer 返回一個 timerId,用于killTimer// 美化顯示標簽ui->label->setStyleSheet("font-size:20px;""background-color:#fff;""border:1px solid black;""border-radius:5px;");ui->label->resize(200, 30); // 設置標簽大小
}// 析構函數
TimerWidget::~TimerWidget()
{delete ui; // 釋放UI對象
}// 定時器事件,每當定時器時間到達時,這個函數會被自動調用
void TimerWidget::timerEvent(QTimerEvent *e)
{Q_UNUSED(e); // 不使用參數// 更新標簽顯示當前系統時間ui->label->setText(QTime::currentTime().toString());
}// “開始”按鈕點擊槽函數
void TimerWidget::on_start_clicked()
{if (timerId == -1) // 如果當前沒有定時器,則啟動timerId = startTimer(1000); // 周期1秒
}// “結束”按鈕點擊槽函數
void TimerWidget::on_stop_clicked()
{if (timerId != -1) // 如果定時器存在{killTimer(timerId); // 停止定時器timerId = -1; // 重置ID}
}
-
定時器是什么?
定時器就像手機鬧鐘,每隔固定時間就“響一次”,Qt會給你一個事件,讓你在timerEvent()
里處理。 -
定時器ID
startTimer()
會返回一個整數ID,用來區分不同的定時器。要關閉定時器必須用killTimer(id)
。 -
事件觸發
每當時間到,Qt會自動調用你重寫的timerEvent(QTimerEvent *e)
函數,你在里面寫想做的事情,比如刷新UI。 -
開始/結束按鈕
on_start_clicked()
:手動開啟定時器on_stop_clicked()
:手動關閉定時器
-
QTimer vs 定時器事件
QTimer
是信號槽方式,更現代,更方便startTimer()
+timerEvent()
是事件驅動方式,更底層,更靈活
timerId == -1
的含義
int timerId; // 定時器ID
timerId = -1; // -1 表示當前沒有定時器
- 當
timerId == -1
時,說明當前沒有定時器在運行。 - 當
timerId != -1
時,說明已經有一個定時器存在,timerId
保存了這個定時器的ID。
如果不判斷,直接調用:
timerId = startTimer(1000);
- 每次點擊“開始”按鈕或構造函數里執行,都會再啟動一個新的定時器。
- 結果就是同一秒內可能有多個定時器同時觸發,導致
timerEvent()
被重復調用,UI更新混亂。
所以加判斷:
if (timerId == -1) timerId = startTimer(1000);
- 確保同一時間只存在一個定時器,避免重復觸發。
Qt能不能同時存在多個定時器?
- 能,Qt允許一個對象啟動多個定時器,每個定時器都有自己的ID。
- 但是你的設計是只需要一個定時器去刷新時間,所以只保留一個,方便管理和關閉。
- 如果你想同時運行多個定時器,需要每個定時器保存自己的ID,在
timerEvent(QTimerEvent *e)
里通過e->timerId()
區分不同的定時器。
七、事件過濾器
Qt中的事件過濾器機制,允許在對象接收到事件之前攔截和處理這些事件。使用事件過濾器可以在不直接修改對象代碼的情況下增加額外的事件處理邏輯。
事件過濾器的用途:
- 監視和響應特定對象的事件,而無需直接修改對象的代碼
- 攔截事件,在事件達到目標對象之前進行預處理或完全阻止事件
- 實現跨多個對象的通用事件處理邏輯,而不需要在每個對象所屬的類中定義重復相同的代碼
事件過濾器的使用步驟:
- 安裝事件過濾器,給要進行事件過濾的對象(組件)安裝事件過濾器
- 開發事件過濾器,對事件進行攔截處理。進行事件攔截處理的對象需要重寫
QObject::eventFilter()
函數進行事件的處理 - 事件處理,如果
eventFilter()
返回true
表示事件處理完畢,如果返回false
,事件將繼續傳遞,最終到達目標對象。
1、安裝事件過濾器
使用QObject::installEventFilter()
函數將一個事件過濾器對象安裝到需要進行事件過濾的對象上。
obj->installEventFilter(this);
在UI可視化界面拖放一個QLabel組件,為該組件安裝事件過濾器:
// 為標簽(label)控件安裝事件過濾器,事件過濾器會監視所有傳遞給該 label 的事件。
//這里this是指向實現了事件過濾邏輯的對象。將事件的處理交給該對象。
ui->label->installEventFilter(this);
2、重寫eventFilter()函數
對事件進行過濾器處理的對象必須重寫QObject::eventFilter()
虛函數。
它的主要作用是在事件傳遞給目標對象之前提供一個攔截點。在事件到達目標對象之前對其進行處理、修改甚至阻止。
watched
: 參數是一個指向被監視對象的指針。這個對象就是調用installEventFilter
函數安裝過濾器的那個對象。event
: 參數是一個指向事件的指針,表示正在傳遞給watched
對象的事件
virtual bool eventFilter(QObject *watched, QEvent *event);
-
watched
-
類型:
QObject*
-
含義:正在被你監視的對象
-
就是你之前調用
installEventFilter(this)
的那個控件。 -
例子:
ui->textEdit->installEventFilter(this);
那么
watched
就可能是ui->textEdit
。 -
作用:通過它你可以知道“這個事件是哪個控件發來的”,這樣你可以針對不同控件做不同處理。
-
-
event
-
類型:
QEvent*
-
含義:正在發生的事件本身
-
事件的類型很多,比如:
QEvent::KeyPress
→ 按鍵事件QEvent::MouseButtonPress
→ 鼠標點擊事件QEvent::FocusIn
→ 焦點進入事件
-
作用:你可以通過它獲取事件的詳細信息,例如:
if(event->type() == QEvent::KeyPress){QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);if(keyEvent->key() == Qt::Key_Return){// 按下的是回車} }
-
watched → 誰發的事件
event → 事件是什么
3、案例
在一個窗口中有一個多行文本輸入框QTextEdit,需要讓我們屏蔽掉鍵盤上的回車鍵,也就是按回車鍵之后在這個文本編輯框中再也不能換行了。
其實上面的需求有三種解決方案:
- 自定義一個新的類讓其繼承QTextEdit,在這個子類中重寫鍵盤事件keyPressEvent,在這個函數里邊屏蔽掉回車鍵
- 自定義一個新的類讓其繼承QTextEdit,在這個子類中重寫事件分發器event,在這個函數里邊屏蔽掉回車鍵
- 給QTextEdit安裝事件過濾器,基于QTextEdit的父窗口對這個控件的事件進行過濾
最簡單的方式還是第三種,因為我們不需要再定義出一個子類就可以輕松的完成控件事件的過濾了。
準備工作:在主窗口中添加一個QTextEdit類型的控件,如下圖:
主窗口頭文件: widget_01.h
class widget_01 : public QWidget
{Q_OBJECTpublic:explicit widget_01(QWidget *parent = nullptr); // 構造函數~widget_01(); // 析構函數// 重寫事件過濾器函數bool eventFilter(QObject *watched, QEvent *event) override;private:Ui::widget_01 *ui; // UI指針
};
主窗口源文件: widget_01.cpp
widget_01::widget_01(QWidget *parent) :QWidget(parent),ui(new Ui::widget_01)
{ui->setupUi(this); // 初始化UI界面// 安裝事件過濾器,把textEdit的事件交給當前對象處理ui->textEdit->installEventFilter(this); // 安裝過濾器后,所有textEdit事件會先到eventFilter
}widget_01::~widget_01()
{delete ui; // 釋放UI對象
}// 事件過濾器函數
bool widget_01::eventFilter(QObject *watched, QEvent *event)
{// 判斷事件來源是否是 textEdit,并且事件類型是否為按鍵事件if(watched == ui->textEdit && event->type() == QEvent::KeyPress){// 將 QEvent 轉換為 QKeyEvent 以獲取按鍵信息QKeyEvent *pKeyEvent = dynamic_cast<QKeyEvent*>(event);// 判斷按鍵是否為回車鍵if(pKeyEvent->key() == Qt::Key_Return){qDebug() << "回車..."; // 打印調試信息return true; // 返回true,表示事件已被處理,不再向下傳遞}} // 對于其他事件,調用基類的eventFilter處理return QWidget::eventFilter(watched, event);
}
事件過濾器作用
- 可以攔截指定對象的事件(如
textEdit
的按鍵、鼠標等事件),在到達對象本身之前先處理。 - 常用于想對某個控件做特殊行為,但不想修改控件內部邏輯的場景。
installEventFilter(this)
- 安裝過濾器后,控件的事件會先到過濾器對象的
eventFilter()
。 - 可以選擇處理(返回
true
)或傳遞(返回false
或調用基類)。
事件類型判斷
event->type()
用于判斷事件類型,比如QEvent::KeyPress
、QEvent::MouseButtonPress
等。
事件消費
- 返回
true
:事件被攔截,不再傳遞給控件自身。 - 返回
false
或調用基類:事件繼續傳遞,控件可自行處理。
類型轉換
- 使用
dynamic_cast<QKeyEvent*>
將QEvent*
轉換為QKeyEvent*
,可以訪問按鍵信息(如key()
)。