一、事件介紹
事件是 應用程序內部或者外部產生的事情或者動作的統稱
- 在 Qt 中使用一個對象來表示一個事件。所有的 Qt 事件均繼承于抽象類 QEvent。
- 事件是由系統或者 Qt 平臺本身在不同的時刻發出的。
- 當用戶按下鼠標、敲下鍵盤,或者是窗口需要重新繪制的時候,都會發出一個相應的事件。
- 一些事件是在用戶操作時發出,如鍵盤事件、鼠標事件等,另一些事件則是由系統本身自動發出,如定時器事件。
事件本身是 操作系統提供的 機制,Qt 也同樣把操作系統事件機制進行了封裝拿到了 Qt 中,但由于 事件 對應的代碼編寫起來不方便,因此 Qt 對于事件機制 又進行了進一步的 封裝,就得到了 信號槽
常見的 QT 事件如下:
不同場景下,要關注的點不一樣。這些事件的子類中就會包含一些對應的不同屬性。
常見事件描述:
從圖片中提取的文字信息如下表所示:
事件名稱 | 描述 |
---|---|
鼠標事件 | 鼠標左鍵、鼠標右鍵、鼠標滾輪,鼠標的移動,鼠標按鍵的按下和松開 |
鍵盤事件 | 按鍵類型、按鍵按下、按鍵松開 |
定時器事件 | 定時時間到達 |
進入離開事件 | 鼠標的進入和離開 |
滾輪事件 | 鼠標滾輪滾動 |
繪屏事件 | 重繪屏幕的某些部分 |
顯示隱藏事件 | 窗口的顯示和隱藏 |
移動事件 | 窗口位置的變化 |
窗口事件 | 是否為當前窗口 |
大小改變事件 | 窗口大小改變 |
焦點事件 | 鍵盤焦點移動 |
拖拽事件 | 用鼠標進行拖拽 |
二、事件的處理
事件處理一般常用的方法為:重寫相關的 Event
函數。
在 Qt 中,幾乎所有的 Event 函數都是虛函數,所以可以重新實現。
比如:在實現鼠標的進入和離開事件時,直接重新實現 enterEvent()
和 leaveEvent()
即可。enterEvent()
和 leaveEvent()
函數原型如下:
🐇標簽提升 & 演示
1)新建 Qt 項目
基類選擇 QWidget,同時勾選 UI 界面文件,并且設計 UI 文件,如下:
- 有了邊框,方便觀察當前鼠標是否進入和離開
這里我們還需要創建 QLabel 子類,來重寫 enterEvent
和 leaveEvent
2)在項目中新添加一個類
先選中項目名稱 QEvent,點擊鼠標右鍵,選擇 add new … ,彈出如下對話框:
3)定義類名并選擇基類
4)此時項目中會新添加以下兩個文件
5)修改基類,重寫 enterEvent()
和 leaveEvent
如果我們想了解這兩個函數,則可以在 幫助文檔 中查找對應的內容
- 要想重寫父類的函數就需要確保寫的函數名字和函數的參數列表完全一致(形參名無所謂)。
- 然后對這兩個函數進行重寫
label.h 和 label.cpp 代碼如下:
但是當前代碼還是有問題的,如下:
- 在 UI 文件中我們可以看到當前在界面上創建的這個 Label 并不是自己寫的 Label,而是 QLabel
- 但是我們需要確保界面上的這個 Label 是自己定義的 Label 類實例才會被執行,因此需要 提升
6)在 UI 文件中選中 Label,右鍵 ——> 提升為…,點擊之后彈出如下:
- 這里需要確保這里填寫的類名以及頭文件 和 上述自定義的 類名頭文件 匹配
💡 通過 “提升為…” 這樣的方式就可以把 Qt Designer
中托上去的控件的類型轉換成自定義的控件類型,如下:
7)執行效果
當鼠標進入設計好的標簽之后,就會在應用程序輸出欄中打印:enterEvent
;鼠標移出設計好的標簽之后,就會在應用程序輸出欄中打印:leaveEvent
。
- 這個時候就說明當前的
enterEvent
和leaveEvent
這兩個事件就被我們給捕獲到了。
🐇 示例 – 當鼠標點擊時,獲取對應的坐標值
該示例主要基于上面代碼,實現:當鼠標點擊時,獲取對應的坐標值
void Label::mousePressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {qDebug() << "按下左鍵";} else if (event->button() == Qt::RightButton) {qDebug() << "按下右鍵";}// 當前 event 對象就包含了鼠標點擊位置的坐標.qDebug() << event->x() << ", " << event->y();// globalX 和 globalY 是以屏幕左上角為原點, 獲取的坐標.qDebug() << event->globalX() << ", " << event->globalY();
}
實現效果如下:
- 我們這里就通過事件 獲取到 鼠標點擊 的位置
mouseEvent
這個函數 其實按左鍵、右鍵、滾輪都可以觸發的,甚至還有 前進 后退側鍵
三、鍵盤按鍵事件
- Qt 中
QShortCut
是信號槽機制封裝過 獲取 鍵盤按鍵的 方式 - 當然我們也可以從更底層角度,通過事件獲取到當前用戶鍵盤按下情況
Qt 中的按鍵事件是通過 QKeyEvent
類來實現的。當鍵盤上的按鍵被按下或者被釋放時,鍵盤事件便會觸發。
在幫助文檔中查找 QKeyEvent
類,然后查找按鍵事件中所有的按鍵類型:在幫助文檔中輸入:Qt::Key
,如下圖:
1. 單個按鍵
示例:當某個按鍵被按下時,輸出:某個按鍵被按下了;
- 新建項目,在頭文件 “widget.h” 中聲明虛函數 keyPressEvent();
- 然后重寫 keyPressEvent() 虛函數,如下圖
2. 組合按鍵
在 Qt 助手中搜索:Qt::KeyboardModifier
,如下圖示:
Qt::KeyboardModifier
中定義了在處理鍵盤事件時對應的修改鍵。
在 Qt 中,鍵盤事件可以與修改鍵以起使用,以實現一些復雜的交互操作。
KeyboardModifier
中修改鍵的具體描述如下:
修飾鍵類型 | 描述 |
---|---|
Qt::NoModifier | 無修改鍵 |
Qt::ShiftModifier | Shift 鍵 |
Qt::ControlModifier | Ctrl 鍵 |
Qt::AltModifier | Alt 鍵 |
Qt::MetaModifier | Meta鍵(在Windows上指Windows鍵,在macOS上指Command鍵) |
Qt::KeypadModifier | 使用鍵盤上的數字鍵盤進行輸入時,Num Lock鍵處于打開狀態 |
Qt::GroupSwitchModifier | 用于在輸入法組之間切換 |
這些修飾鍵常用于編程中處理鍵盤事件,特別是在使用Qt框架開發跨平臺應用程序時。
代碼如下:
void Widget::keyPressEvent(QKeyEvent *event)
{if(event->key() == Qt::Key_A){qDebug() << "A 按鍵被按下";}if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_A){qDebug() << "Ctrl + A 按鍵被按下";}
}
四、鼠標事件
在 Qt 中,鼠標事件是用 QMouseEvent 類
來實現的。當在窗口中按下鼠標或者移動鼠標時,都會產生鼠標事件。
- 利用
QMouseEvent 類
可以獲取鼠標的哪個鍵被按下了以及鼠標的當前位置等信息。
在 Qt 幫助文檔中查找 QMouseEvent 類
如下圖示:
鼠標單擊 | 釋放 | 雙擊 | 移動 事件
① 在 Qt 中,鼠標按下是通過虛函數 mousePressEvent() 來捕獲的。mousePressEvent() 函數原型如下:
- [ virtual protected] void QWidget:: mousePressEvent (QMouseEvent * event )
鼠標左右鍵及滾的表示如下:
-
Qt::LeftButton 鼠標左鍵
-
Qt::RightButton 鼠標右鍵
-
Qt::MidButton 鼠標滾輪
void Label::mousePressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {qDebug() << "按下左鍵";} else if (event->button() == Qt::RightButton) {qDebug() << "按下右鍵";}
}
② 鼠標釋放事件是通過虛函數 mouseReleaseEvent() 來捕獲的。mouseReleaseEvent() 函數原型如下:
- [ virtual protected] void QWidget:: mouseReleaseEvent (QMouseEvent * event )
void Label::mouseReleaseEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {qDebug() << "釋放左鍵";} else if (event->button() == Qt::RightButton) {qDebug() << "釋放右鍵";}
}
③ 鼠標雙擊事件是通過虛函數:mouseDoubleClickEvent() 來實現的。mouseDoubleClickEvent() 函數原型如下:
- [ virtual protected] void QWidget:: mouseDoubleClickEvent (QMouseEvent * event )
void Label::mouseDoubleClickEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {qDebug() << "雙擊左鍵";} else if (event->button() == Qt::RightButton) {qDebug() << "雙擊右鍵";}
}
④ 鼠標移動事件是通過虛函數:mouseMoveEvent() 來實現的。同時為了實時捕獲鼠標位置信息,需要通過函數 setMouseTracking() 來追蹤鼠標的位置。mouseMoveEvent()函數原型如下:
- [ virtual protected] void QWidget:: mouseMoveEvent (QMouseEvent * event )
setMouseTracking() 函數原型如下:
- void setMouseTracking( bool enable )
說明:setMouseTracking() 函數默認是 false,需要設置為 true,才能實時捕獲鼠標位置信息。
否則只有當鼠標按下時才能捕獲其位置信息。
- 上面的操作其實和 二中說的標簽提升 那里一樣,可以仔細看看
最后演示結果如下:
- 這里演示的話,還是把實時捕捉關了,敏感太高不適合演示現象,我們這里坐標可以長按拖拽鼠標也可以顯示 鼠標位置信息
- 我們這里重寫鼠標事件的操作都是放在 自定義的 Label 中完成的,此時只有鼠標在 Label 范圍內進行動作才能捕捉到
- 當前也可以把其放到 Widget(Qwidget 子類) 來完成,此時鼠標在整個窗口都可以捕捉到
五、定時器
Qt 中在進行窗口程序的處理過程中,經常要周期性的執行某些操作,或者制作一些動畫效果,使用定時器就可以實現。
所謂 定時器就是在間隔一定時間后,去執行某一個任務
定時器在很多場景下都會使用到,如彈窗自動關閉之類的功能等。
Qt 中的定時器分為 QTimerEvent
和 QTimer
這 2 個類。
QTimerEvent
類用來描述一個定時器事件。- 在使用時需要通過 startTimer() 函數來開啟?個定時器,這個函數需要輸入一個以毫秒為單位的整數作為參數來表明設定的時間,它返回的整型值代表這個定時器。
- 當定時器溢出時(即定時時間到達)就可以在 timerEvent() 函數中獲取該定時器的編號來進行相關操作。
QTimer
類來實現一個定時器,它提供了更高層次的編程接口,如:可以使用信號和槽,還可以設置只運行一次的定時器。
1. QTimerEvent 類
聯系前文:【QT】 控件 – 顯示類
在 UI 界面上放置一個 LCD Number 控件,讓其 10 秒數字不斷遞減到 0,相當于倒計時。
- 新建項目,在 UI 界面文件放置一個 LCD Number 控件,并且給定初始值為 10
- 在 “widget.h” 頭文件中聲明
timerEvent()
函數,并定義一個整型變量 - 在 “widget.cpp” 文件中重寫
timerEvent()
函數
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 開啟定時器事件// 此處 timerId 是一個定時器的身份標識(類似于 Linux 中的文件描述符)timerId = this->startTimer(1000); // 這里 timerId 設為成員變量
}void Widget::timerEvent(QTimerEvent *event)
{// 如果一個程序中存在多個定時器 (startTimer 創建的定時器), 此時每個定時器都會觸發 timerEvent 函數.// 先判定一下這次觸發是否是想要的定時器觸發的.if (event->timerId() != this->timerId) {// 如果不是我們的定時器觸發的, 就直接忽略.// 當前程序中只有這一個定時器.return;}int value = ui->lcdNumber->intValue();if (value <= 0) {// 停止定時器this->killTimer(this->timerId);return;}value -= 1;ui->lcdNumber->display(value);
}
- 此時運行程序就可以獲得我們想要的倒計時結果了
但是相比于 QTimer
,使用 timerEvent
還是要更加復雜一點,因為需要手動管理 timerId
,需要區分整個函數調用是由哪個 timer 引起
2. QTimer 類
純代碼實現,無調用 ui
- 實現基本的計時功能
- 還實現了**【獲取系統日期及實時時間】**,如下:
#include "widget.h"
#include "ui_widget.h"#include <QVBoxLayout>
#include <QLabel>
#include <QTimer>
#include <QPushButton>
#include <QDateTime>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QVBoxLayout *layout = new QVBoxLayout(this);// 創建控件QLabel *label = new QLabel("0");label->setStyleSheet("font-size: 50px; color: red;");QLabel *label_date = new QLabel("0");label_date->setStyleSheet("font-size: 50px; color: blue;");QPushButton *startBtn = new QPushButton("開始");QPushButton *stopBtn = new QPushButton("停止");// 按鈕布局QHBoxLayout *btnLayout = new QHBoxLayout;btnLayout->addWidget(startBtn);btnLayout->addWidget(stopBtn);// 主布局layout->addWidget(label, 0, Qt::AlignCenter);layout->addWidget(label_date, 0, Qt::AlignCenter);layout->addLayout(btnLayout);// 創建定時器和計數器QTimer *timer = new QTimer(this);// 連接信號與槽connect(timer, &QTimer::timeout, [=](){static int num=1;label->setText(QString::number(num++));});connect(timer, &QTimer::timeout, [=](){QString str=QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");label_date->setText(str);});connect(startBtn, &QPushButton::clicked, [=](){timer->start(1000);});connect(stopBtn, &QPushButton::clicked, [=]{timer->stop();});
}
結果如下:
六、事件分發器
1. 概述
在 Qt 中,事件分發器(Event Dispatcher)是一個核心概念,用于處理 GUI 應用程序中的事件。
事件分發器負責 將事件從一個對象傳遞到另一個對象,直到事件被處理或被取消。
每個繼承自 QObject 類
或 QObject 類
本身都可以在本類中 重寫 bool event(QEvent *e)
函數,來實現相關事件的捕獲和攔截。
2. 事件分發器工作原理
在 Qt 中,我們發送的事件是傳給了 QObject
對象的 event()
函數。
- 所有的事件都會進入到這個函數里面,那么我們處理事件就要重寫這個
event()
函數。 - event() 函數本身不會去處理事件,而是根據 事件類型(type值)調用不同的事件處理函數。事件分發器就是工作在應用程序向下分發事件的過程中,如下圖:
如上圖,事件分發器用于分發事件。在此過程中,事件分發器也可以做 攔截操作
- 事件分發器主要是通過
bool event(QEvent *e)
函數來實現。其返回值為布爾類型,若為 true,代表攔截,不向下分發。 - Qt 中的事件是封裝在 QEvent類 中,在 Qt 助手中輸入
QEvent
可以查看其所包括的事件類型,如下圖示:
演示代碼如下:
void Widget::mousePressEvent(QMouseEvent *event)
{if(event->button() == Qt::LeftButton){qDebug() << "鼠標左鍵按下";}
}bool Widget::event(QEvent *ev)
{if(ev->type() == QEvent::MouseButtonPress){qDebug() << "Event 中鼠標被按下";return true; // 表示不向下分發}// 其他事件交給父類處理(默認)return QWidget::event(ev);
}
結果如下:
當我們 return false 時,就會出現一下結果
- 單機鼠標右鍵,鼠標被按下
- 單擊鼠標左鍵, event 函數和 mousePressEvent 函數交替觸發
七、事件過濾器
在 Qt 中,一個對象可能經常要查看或攔截另外一個對象的事件,如對話框想要攔截按鍵事件,不讓別的組件接收到,或者修改按鍵的默認值等。
- 通過上面的學習,我們已經知道,Qt 創建了 QEvent 事件對象之后,會調用
QObject
的event()
函數 處理事件的分發。 - 顯然,我們可以在 event() 函數中實現攔截的操作。由于 event() 函數是 protected 的,因此,需要繼承已有類。
- 如果組件很多,就需要重寫很多個 event() 函數。這當然相當?煩,更不用說重寫 event() 函數還得小心一堆問題。好在 Qt 提供了另外一種機制來達到這一目的:事件過濾器
事件過濾器是在 應用程序 分發到 event 事件分發器 之前,再做一次更高級的攔截
事件過濾器的?般使用步驟:
- 安裝事件過濾器;
- 重寫事件過濾器函數:
eventfilter()
【示例】:基于文章上面演示的 標簽提升 那的操作,在 "Label.cpp" 中代碼如下:
#include "label.h"
#include <QDebug>
#include <QMouseEvent>Label::Label(QWidget* parent): QLabel(parent)
{}void Label::mousePressEvent(QMouseEvent *event)
{QString str = QString("鼠標按下: x = %1, y = %2").arg(event->x()).arg(event->y());qDebug() << str.toUtf8().data();
}bool Label::event(QEvent *e)
{//如果是鼠標按下,在event事件分發時做攔截操作if (e->type() == QEvent::MouseButtonPress){QMouseEvent *event = static_cast<QMouseEvent *>(e);QString str = QString("Event函數中鼠標按下: x = %1, y = %2").arg(event->x()).arg(event->y());qDebug() << str.toUtf8().data();return true; //返回true,代表用戶自己處理,不向下分發}//其他事件交給父類處理return QLabel::event(e);
}
Widget.cpp
代碼如下:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QEvent>
#include <QMouseEvent>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 給 label 裝上事件過濾器 this;當前窗口安裝事件過濾器ui->label->installEventFilter(this);
}bool Widget::eventFilter(QObject *obj, QEvent *e)
{if(obj == ui->label) // 判斷控件{if(e->type() == QEvent::MouseButtonPress){QMouseEvent *event = static_cast<QMouseEvent *>(e);QString str = QString("事件過濾器中鼠標按下: x = %1, y = %2").arg(event->x()).arg(event->y());qDebug() << str.toUtf8().data();return true; // 返回true,代表用戶自己處理,不向下分發}}return QWidget::eventFilter(obj, e);
}
- 注意書寫函數實現時,記得先聲明函數名
結果如下:
八、其他
moveEvent
:窗口移動時觸發的事件resizeEvent
:窗口大小改變時觸發的事件