QT-事件

Qt事件

除了信號和槽通信機制外,Qt中還提供了事件處理機制實現與用戶的交互和對象間的通信。Qt捕獲底層操作系統消息,進行封裝之后轉換為Qt事件,事件處理后才發出信號。

一、事件概述

Qt中事件是程序內部或外部發生的動作。比如程序外部:用戶移動鼠標、單擊鼠標、鼠標拖拽、按下按鍵等操作都會產生對應的事件。程序內部:窗口第一次顯示、放大、縮小等。

📌 兩類事件

  1. 外部事件(用戶操作導致的)
    就是人跟程序互動時觸發的,比如:

    • 移動鼠標 → 程序收到一個“鼠標移動事件”。
    • 單擊鼠標 → 程序收到一個“鼠標點擊事件”。
    • 按下鍵盤 → 程序收到一個“鍵盤按下事件”。
    • 拖動窗口 → 程序收到“鼠標拖拽事件”。

    👉 就好比你在敲門、按電梯按鈕,程序必須做出反應。


  1. 內部事件(程序自己觸發的)
    就算你啥都不點,程序自己也會產生一些動作,比如:

    • 窗口 第一次顯示 → 產生“窗口顯示事件”。
    • 窗口被 放大/縮小 → 產生“窗口大小改變事件”。
    • 程序定時器到點了 → 產生“定時器事件”。

    👉 就像手機自己亮屏、鎖屏,這是系統自己觸發的。

1、事件來源

事件有兩個來源:程序外部和程序內部

  • 在與用戶交互時產生,比如用戶點擊鼠標、按下按鍵等,此時操作系統會感知到用戶的行為,并產生消息,然后將消息投遞到應用程序的消息隊列當中;應用程序從消息隊列中提取消息,并將其轉化為Qt事件,生產事件對象。
  • 由Qt應用程序自身產生。例如當窗口第一次顯示時,會產生一個繪制事件,以通知窗口需要重新繪制自身,從而使窗口可見。這是由程序內部產生的事件。
2、Qt事件處理機制

(1)當操作系統發生一個事件時,事件首先會被操作系統內核中的設備驅動程序所感知,然后發送給操作系統的事件管理系統,事件管理系統將其放入到事件隊列中。

(2)Qt應用程序作為一個客戶端,通過調用QApplication的exec()函數啟動事件循環,這個循環會不斷地從事件隊列取出事件,Qt捕獲之后,會將該事件轉換為相應的Qt事件對象。

(3)事件自己不能處理自己,循環中依次取出事件首先交給notify()函數,通過notify()函數派發給處理事件的對象。由QObject類以及其派生類對象進行事件的處理,通過重寫event()函數,或重寫對應的事件處理函數完成事件的處理。

在這里插入圖片描述

完整流程總結

  1. 程序運行后,QApplication::exec() 啟動事件循環,持續監聽事件;
  2. 事件產生后,先到 QApplication::notify() 做全局處理;
  3. 再傳遞到目標 QObjectevent() 方法,識別事件類型;
  4. 最終調用具體的 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);}
}
  1. mousePressEvent(QMouseEvent *e)
    👉 處理鼠標按下事件。
  • 左鍵:記錄點擊位置(用于窗口拖動)。
  • 右鍵:只是輸出調試信息。
  1. wheelEvent(QWheelEvent *e)
    👉 處理鼠標滾輪事件。
  • 向上滾:放大圖片。
  • 向下滾:縮小圖片。
  1. 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) 這些函數?

  1. Qt 已經幫我們定義了這些事件函數,但默認實現是“什么都不做”
    • 比如 mousePressEvent() 默認不會幫你輸出坐標,也不會幫你移動窗口。
    • 如果你需要自定義行為,就要 重寫 它們,寫上自己的邏輯。
  2. 讓控件有“特殊功能”
    • 例子:重寫 mousePressEvent,實現點擊后窗口能拖動。
    • 如果不重寫,窗口只能被系統默認拖動(有標題欄時),去掉邊框后根本沒法拖動。
  3. 事件驅動編程的核心
    • Qt 是 事件驅動 的:鼠標、鍵盤、窗口變化都會產生事件。
    • 程序如何響應這些事件,就要靠重寫事件函數。

🔹 舉個通俗例子:

事件函數 想象成「門鈴」。

  • Qt 框架:默認幫你裝好一個門鈴(函數)。
  • 系統按門鈴(鼠標點擊 / 滾輪滾動 / 按鍵),就會觸發事件。
  • 如果你沒寫(不重寫),按門鈴沒人理(默認什么都不做)。
  • 如果你寫了(重寫),就能定義:
    • 門鈴響時開燈
    • 門鈴響時開門
    • 門鈴響時發個消息

? 所以:
重寫事件函數 = 告訴程序當某個事件發生時,你要如何反應。

四、鍵盤事件

1、鍵盤事件處理函數
事件處理函數對應事件類型參數類型描述
void keyPressEvent(QKeyEvent *event);QEvent::KeyPressQKeyEvent按鍵按下
void keyReleaseEvent(QKeyEvent *event);QEvent::KeyReleaseQKeyEvent按鍵釋放

下面是處理鍵盤事件的示例:

通過按鍵移動圖片,重寫鍵盤按下事件的處理函數。

#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::WindowNoState0000默認普通狀態
Qt::WindowMinimized0001最小化
Qt::WindowMaximized0010最大化
Qt::WindowFullScreen0100全屏

如果 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()實現事件處理示例

  1. 新建MyWidget類
  2. 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       // 頭文件保護宏結束
  1. event(QEvent *event)
    • Qt 的 事件分發函數
    • 凡是傳給這個窗口的事件(鼠標、鍵盤、窗口大小變化等),都會先經過它。
    • 如果你重寫它,可以統一攔截和處理不同類型的事件。
  2. doPressEvent(QMouseEvent *event)
    • 這是你 自定義的函數,不是 Qt 框架強制要求的。
    • 一般用來在 event() 里檢測到鼠標事件后,專門調用它來處理。
    • 相當于你自己封裝的“鼠標事件處理邏輯”。
  3. 返回值 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中的事件過濾器機制,允許在對象接收到事件之前攔截和處理這些事件。使用事件過濾器可以在不直接修改對象代碼的情況下增加額外的事件處理邏輯。

事件過濾器的用途:

  • 監視和響應特定對象的事件,而無需直接修改對象的代碼
  • 攔截事件,在事件達到目標對象之前進行預處理或完全阻止事件
  • 實現跨多個對象的通用事件處理邏輯,而不需要在每個對象所屬的類中定義重復相同的代碼

事件過濾器的使用步驟:

  1. 安裝事件過濾器,給要進行事件過濾的對象(組件)安裝事件過濾器
  2. 開發事件過濾器,對事件進行攔截處理。進行事件攔截處理的對象需要重寫QObject::eventFilter()函數進行事件的處理
  3. 事件處理,如果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);
  1. watched

    • 類型:QObject*

    • 含義:正在被你監視的對象

    • 就是你之前調用 installEventFilter(this) 的那個控件。

    • 例子:

      ui->textEdit->installEventFilter(this);
      

      那么 watched 就可能是 ui->textEdit

    • 作用:通過它你可以知道“這個事件是哪個控件發來的”,這樣你可以針對不同控件做不同處理。

  2. 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,需要讓我們屏蔽掉鍵盤上的回車鍵,也就是按回車鍵之后在這個文本編輯框中再也不能換行了。

其實上面的需求有三種解決方案:

  1. 自定義一個新的類讓其繼承QTextEdit,在這個子類中重寫鍵盤事件keyPressEvent,在這個函數里邊屏蔽掉回車鍵
  2. 自定義一個新的類讓其繼承QTextEdit,在這個子類中重寫事件分發器event,在這個函數里邊屏蔽掉回車鍵
  3. 給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::KeyPressQEvent::MouseButtonPress 等。

事件消費

  • 返回 true:事件被攔截,不再傳遞給控件自身。
  • 返回 false 或調用基類:事件繼續傳遞,控件可自行處理。

類型轉換

  • 使用 dynamic_cast<QKeyEvent*>QEvent* 轉換為 QKeyEvent*,可以訪問按鍵信息(如 key())。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921249.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921249.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921249.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HI3519DRFCV500/HI3519DV500海思核心板IPC算力2.5T圖像ISP超高清智能視覺應用提供SDK軟件開發包

Hi3519DV500是一顆面向視覺行業推出的超高清智能 SoC。最高支持四路sensor輸入&#xff0c;支持最高4K30fps的ISP圖像處理能力&#xff0c;支持 2F WDR、多級降噪、六軸防抖、全景拼接、多光 譜融合等多種傳統圖像增強和處理算法&#xff0c;支持通過AI算法對輸入圖像進行實時降…

go 初始化組件最佳實踐

Go 語言初始化最佳實踐 在 Go 語言中, 有一個 init() 函數可以對程序進行包級別的初始化, 但 init() 函數有諸多不便, 例如: 無法返回錯誤, 進行耗時初始化時, 會增加程序啟動時間。因此 init() 函數并不適用于所有初始化。 1.初始化方式 在程序進行初始化時&#xff0c;我們應…

域名暫停解析是怎么回事

域名注冊和使用是需要付費的&#xff0c;如果沒有及時續費&#xff0c;域名注冊商就會暫停該域名的解析服務。相關數據顯示&#xff0c;大約有 30% 的域名暫停解析情況是由于欠費引起的。比如&#xff0c;有個小公司的網站域名到期了&#xff0c;負責續費的員工忘記操作&#x…

前端開發的“三劍客”—— ??HTML、CSS、JavaScript??

前端開發的“三劍客”—— ??HTML、CSS、JavaScript??&#xff0c;是構建所有網頁和Web應用的基石。它們分工明確又緊密協作&#xff0c;共同實現了網頁的“內容結構”“視覺表現”和“交互行為”。以下是三者的詳細解析及協作邏輯&#xff1a;??1. HTML&#xff1a;網頁…

TDengine TIMEDIFF() 函數用戶使用手冊

TDengine TIMEDIFF() 函數詳細使用手冊 目錄 功能概述函數語法參數說明返回值說明版本變更說明技術特性使用場景及示例時間單位處理數據類型兼容性注意事項常見問題最佳實踐 功能概述 TIMEDIFF() 函數用于計算兩個時間戳的差值&#xff0c;返回 expr1 - expr2 的結果。結果…

數據結構:棧和隊列(上)

匯總代碼見&#xff1a;登錄 - Gitee.com 上一篇文章&#xff1a;數據結構&#xff1a;雙向鏈表-CSDN博客 與本文相關的結構體傳參&#xff1a;自定義類型&#xff1a;結構體-CSDN博客 1.棧 1.1概念和結構 棧&#xff1a;一種特殊的線性表&#xff0c;其只允許在固定的一端…

文檔抽取技術:提取非結構化文檔中的關鍵信息,提升檔案管理、金融保險和法律合規領域的效率與準確性

在信息爆炸的時代&#xff0c;各種機構、企業等都面臨著海量非結構化文檔數據的挑戰。報告、合同、票據、檔案記錄、法律文書等文檔中蘊藏著巨大的數據&#xff0c;但傳統依靠人工閱讀、理解和錄入的方式效率低下、成本高昂且容易出錯。文檔抽取技術作為人工智能和自然語言處理…

雷柏VT1 MAX評測:原生中小手形電競鼠標 但既不僅限于中小手形 也不僅限于電競

一、前言&#xff1a;真正針對中小手形設計的電競鼠標 雷柏第二代VT系列電競鼠標我們已經體驗過很多款了&#xff0c;基本都是針對大中手形設計的外形模具&#xff0c;只有VT3s系列是VT3系列的縮小版&#xff0c;更適合中小手形使用&#xff0c;但也只是對中大手形模具重新優化…

新客戶 | TDengine 時序數據庫賦能開源鴻蒙物聯展區實時監控與展示

在工業物聯網快速發展的當下&#xff0c;企業普遍面臨著兩大挑戰&#xff1a;一是設備種類繁多、接入標準不一&#xff0c;導致系統建設容易陷入“數據孤島”&#xff1b;二是實時監控和多場景聯動的需求越來越強烈&#xff0c;但傳統數據庫在高頻寫入與多維分析上難以兼顧&…

深入剖析 ConcurrentHashMap:Java 并發編程的基石

目錄 【1】Java 7 中 ConcurrentHashMap 的實現原理 1.分段鎖&#xff08;Segment&#xff09; 2. 數據結構 3. 操作流程 【2】Java 8 中 ConcurrentHashMap 的改進 1.紅黑樹的引入 2.CAS 操作 3.數據結構的變化 【3】ConcurrentHashMap 的常用方法及使用示例 1.put(…

【會員專享數據】2020-2022年我國鄉鎮的逐日地表氣壓數據(Shp/Excel格式)

之前我們分享過2020—2022年中國0.01分辨率逐日地表氣壓柵格數據&#xff08;可查看之前的文章獲悉詳情&#xff09;&#xff01;該數據是研究者張凌, 胡英屹等發布在國家冰川凍土沙漠科學數據中心平臺上的高分辨地表氣壓數據。很多小伙伴拿到數據后反饋柵格數據不太方便使用&a…

第二階段WinForm-12:UI控件庫

1_驗證碼與條形碼 1.1_條碼基礎知識 條碼&#xff1a;條碼是由一組按一定編碼規則排列的條、空符號組成&#xff0c;用以表示一定的字符、數字及符號組成的信息 1.2_一維碼 &#xff08;1&#xff09;Code 128 Code 128 是一種密度很高的字母數字代碼系統&#xff0c;可對其…

別再誤會了!Redis 6.0 的多線程,和你想象的完全不一樣

技術解析核心誤區&#xff1a;Redis 6.0是完全多線程的嗎&#xff1f;No. Redis 6.0引入的多線程&#xff0c;只用于網絡I/O的讀寫和數據的解析。而核心的命令執行&#xff08;比如 GET, SET, HGETALL 等&#xff09;依然是單線程的。Redis的架構演進&#xff0c;就像是把一個復…

23種設計模式——抽象工廠模式(Abstract Factory Pattern)詳解

?作者簡介&#xff1a;大家好&#xff0c;我是 Meteors., 向往著更加簡潔高效的代碼寫法與編程方式&#xff0c;持續分享Java技術內容。 &#x1f34e;個人主頁&#xff1a;Meteors.的博客 &#x1f49e;當前專欄&#xff1a;設計模式 ?特色專欄&#xff1a;知識分享 &#x…

本地部署開源數據生成器項目實戰指南

本地部署開源數據生成器項目實戰指南 前言 在當今大數據和人工智能時代&#xff0c;高質量數據集對于模型訓練和算法開發至關重要。然而&#xff0c;獲取真實且合規的數據集往往面臨隱私、成本和法律等多重挑戰。合成數據生成技術為此提供了優雅的解決方案&#xff0c;它能夠…

2025React面試題集錦

1. React 是什么?它有哪些主要特點? React 是由Facebook開發的開源JavaScript庫,用于構建用戶界面(UI),尤其適合開發復雜的單頁應用(SPA)。 主要特點: 聲明式編程:只需描述UI應該是什么樣子(如return <div>Hello</div>),React會自動處理DOM更新,無需…

設計模式:迭代器模式(Iterator Pattern)

文章目錄一、概念二、實例分析三、示例代碼一、概念 迭代器模式 是一種 行為型設計模式&#xff0c;用于在不暴露集合對象內部結構的前提下&#xff0c;順序訪問集合中的元素。 換句話說&#xff1a; 集合類只負責數據存儲&#xff1b;迭代器類負責遍歷集合&#xff1b;使用者…

Vue 3 學習路線指南

階段一:基礎入門 (1-2周) 1.1 環境準備 # 安裝 Node.js (推薦 18+ 版本) # 安裝 Vue CLI 或使用 Vite npm create vue@latest my-vue-app cd my-vue-app npm install npm run dev1.2 Vue 3 核心概念 響應式系統:ref(), reactive(), computed() 組合式 API:setup() 函數 模…

使用 `hover:not-[:has(:hover)]` 避免「父元素和子元素同時 hover」時的樣式沖突

:hover:not-(:has(:hover)) has() CSS 4 引入的“父選擇器”&#xff0c;意思是&#xff1a;匹配那些里面包含某個子元素/狀態的元素。 例如&#xff1a;:has(:hover) 表示「自身包含正在被 hover 的子元素」。 :not() 取反偽類&#xff0c;表示不匹配里面的條件。 比如我…

第三十天-DMA串口實驗

一、DMA概述二、DMA通道注意&#xff0c;想要往串口中寫數據&#xff0c;外部請求信號應該是USARTx_TX&#xff0c;當DR寄存器為空時&#xff0c;產生TX信號&#xff0c;請求DMA。反之&#xff0c;從串口中讀數據&#xff0c;外部請求信號應該是USARTx_RX&#xff0c;當DR寄存器…