QT學習教程(三十五)

事件處-? Event Processingn

事件是視窗系統或者Qt 本身在各種不同的情況下產生的。當用戶點擊或者釋放鼠標,鍵盤時,一個鼠標事件或者鍵盤事件就產生了。當窗口第一次顯示時,一個繪制事件會產生告訴新可見的窗口繪制自己。很多事件是為了相應用戶動作產生的,也有一些事件是由 系統獨立產生的。 在用Qt 編程時,我們很少要考慮事件,當一些事件發生時,Qt 控件會發出相應的信號。只有當實現用戶控件或者需要修改現有控件的行為時,我們才需要考慮事件。事件不能和信號混淆。一般來講,在使用控件時需要處理的是信號,在實現一個控件時需要處理事件。例如,我們使用QPushButton 時,我們只要clicked()信號就可以了,而不用管鼠標點擊事件。但是如果我們實現一個像 QPushButton 這樣的類,我們就需要處理鼠標或者鍵盤事件 ,發出clicked()信號。

重寫事件處理函數(Reimplementing Event Handlers

在 Qt 中,一個事件是 QEvent 的子類的對象。Qt 能夠處理上百種類型的事件,每一類型的事件由一個枚舉值確定。例如,對鼠標點擊事件,QEvent::type()返回的值為 QEvent::MouseButtonPress。

很多情況下,一個 QEvent 對象不能保存有關事件的所有信息,例如,鼠標點擊事件需要保存是左鍵還是右鍵觸發了這個信息,還要知道事件發生時鼠標指針的位置,這些額外的信息儲存在 QEvent 的子類QMouseEvent 中。

Qt 的對象通過QObject::event()得到有關事件的信息。QWidget::event()提供了很多普通類型的信息,實現了很多事件處理函數,例如 mousePressEvent(),keyPressEvent(),paintEvent()等等。

在前面的章節中,我們已經在MainWindow 類,IconEditor 類,Plotter 類中看到了很多事件處理函數,在QEvent 參考文檔中,還列舉了很多類型的事件。

我們還可以定義自己的事件,把事件分派出去。這里,我們討論一下兩種最常用的事件:鍵盤事件和時間事件。

重寫函數 keyPressEvent()和keyReleaseEvent()可以處理鍵盤事件。 Plotter 控件就重寫了 keyPressEvent()函數。通常,我們只需要重寫 keyPressEvent(),需要處理鍵盤釋放事件的只有修改鍵(Ctrl, Shift, Alt),而這些鍵的信息可以通過 QKeyEvent::modifiers()得到。例如,如果我們重寫了控件 CodeEditor 控件的 KeyPressEvent()函數,區分Home 鍵和 Ctrl+Home 鍵:

void CodeEditor::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) { case Qt::Key_Home:
if (event->modifiers() & Qt::ControlModifier) { goToBeginningOfDocument();
} else {
goToBeginningOfLine();
}
break;
case Qt::Key_End:
...
default:
QWidget::keyPressEvent(event);
}
}

Tab 鍵和Backtab(Shift+Tab)鍵很特殊,它們是在控件調用 keyPressEvent()之前,由 QWidget::event()處理的,這兩個鍵的作用是把輸入焦點轉到前一控件或者下一個控件上,在 CodeEditor 中,希望 Tab 鍵的作用是縮進,可以這樣重寫 event():

bool CodeEditor::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); if (keyEvent->key() == Qt::Key_Tab) {
insertAtCurrentPosition('\t'); return true;
}
}
return QWidget::event(event);
}

如果這個事件是一個鍵盤敲擊事件,我們把 QEvent 對象轉換成QKeyEvent,然后確定是那個鍵敲擊了,如果是 Tab 鍵,進行處理后返回 true,通知 Qt 我們已經對事件進行了處理。如果返回 false,Qt 還會把這個事件交給基類控件處理。

響應鍵盤事件的更好的方法是使用 QAction。例如,goToBeginningOfLine()和 goToBeginningOfDocument()是CodeEditor 的兩個公有槽函數, CodeEditor 是MainWindow 的中央控件,下面的代碼實現了鍵盤和槽函數的綁定:

MainWindow::MainWindow()
{
editor = new CodeEditor; setCentralWidget(editor); goToBeginningOfLineAction =
new QAction(tr("Go to Beginning of Line"), this); goToBeginningOfLineAction->setShortcut(tr("Home")); connect(goToBeginningOfLineAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfLine())); goToBeginningOfDocumentAction =
new QAction(tr("Go to Beginning of Document"), this); goToBeginningOfDocumentAction->setShortcut(tr("Ctrl+Home")); connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfDocument()));
...
}

這樣可以很容易把一個鍵盤敲擊的命令加入到菜單或者工具條中。如果命令沒有出現在用戶界面中,可用用 QShortcut 對象代替QAction 對象,在QAction內部就是使用這個類實現鍵盤的綁定。

通常情況下,只要窗口中有激活的控件,控件上用 QAction 和 QShortcut 設置的鍵盤綁定都是可用的。綁定的鍵可用 QAction::setShortcutContext()或者 QShortcur::setContext()進行修改。

另一個常用的事件類型是時間事件。其他事件都是由用戶的某種活動引發的,而時間事件則使程序按照一定的時間間隔執行特定的任務。時間事件一般用來使光標閃爍,或者播放動畫,或者只是繪制顯示界面或者控件。

為了介紹時間事件,我們將實現一個 Ticker 控件。這個控件顯示一條標語,每隔 30 毫秒鐘向左移動一個象素。如果控件比標語要寬,標語的文本重復的顯示在控件上,填滿整個控件。

#ifndef TICKER_H #define TICKER_H
#include <QWidget>
class Ticker : public QWidget
{Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText) public:
Ticker(QWidget *parent = 0);
void setText(const QString &newText); QString text() const { return myText; } QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event); void timerEvent(QTimerEvent *event); void showEvent(QShowEvent *event); void hideEvent(QHideEvent *event);
private:
QString myText; int offset;
int myTimerId;
};
#endif

在頭文件中,我們實現了 Ticker 的四個事件處理函數,其中三個 timeEvent(), showEvent()和hideEvent()是我們以前沒有見過的。

#include <QtGui> #include "ticker.h"
Ticker::Ticker(QWidget *parent)
: QWidget(parent)
{
offset = 0;
myTimerId = 0;
}

在構造函數中,設置offset 為 0,這個變量是文本要顯示的x 坐標值。時間ID 總是非 0 的,這里設置myTimerId 為 0 說明我們還沒有啟動任何時間

void Ticker::setText(const QString &newText)
{
myText = newText; update(); updateGeometry();
}

函數 setText()設置要顯示的文本。調用 update()引發繪制事件重新顯示文本, updateGeometry()通知布局管理器改變控件的大小。

QSize Ticker::sizeHint() const
{return fontMetrics().size(0, text());
}

函數 sizeHint()返回的是控件在不同文本時完整顯示所需的尺寸。 QWidget::fontMetrics()返回一個 QFontMetrics 對象,得到控件所用的字體的信息。在這里我們需要得到的是文本的大小。(在 QFontMetrics::size()中,第一個參數是一個標識,對字符串來講并不需要,所有賦了 0 值)。

void Ticker::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
int textWidth = fontMetrics().width(text()); if (textWidth < 1)
return;
int x = -offset;
while (x < width()) {
painter.drawText(x, 0, textWidth, height(), Qt::AlignLeft | Qt::AlignVCenter, text());
x += textWidth;
}
}

函數paintEvent()使用QPainter::drawText()繪制文本。調用fontMetrics()得到文本所需要的水平空間,然后多次繪制文本,直至填滿整個控件

void Ticker::showEvent(QShowEvent * /* event */)
{
myTimerId = startTimer(30);
}

showEvent()啟動了一個計時器。調用QObject::startTimer()返回一個ID值,這個 ID 值可以幫助我們識別這個計時器。QObject 能夠支持多個獨立的不同的時間間隔的計時器。調用 startTimer()以后,Qt 大約每 30 毫秒產生一個事件,時間的準確與否取決于不同的操作系統。

我們也可以在 Ticker 的構造函數中調用 startTimer()。但是在控件可見以后再啟動,能夠節省一些資源。

void Ticker::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
++offset;
if (offset >= fontMetrics().width(text())) offset = 0;
scroll(-1, 0);
} else {
QWidget::timerEvent(event);}
}

函數 timerEvent()由系統以一定間隔進行調用的。把offset 增加 1 來模仿文字的移動,增加到標語的寬度時文字的寬度是重新設置為 0。然后調用scroll()把控件向左滾動一個象素。也可以調用 update(),但是 scroll()更加高效,它對可見的象素進行移動,只是對需要新繪制的地方調用繪制事件(在這個例子中 ,只是一個象素寬的區域)。

如果計時器不是我們需要處理的,則把它傳遞給基類。

void Ticker::hideEvent(QHideEvent * /* event */)
{
killTimer(myTimerId);
}

在 hideEvent()中,調用QObject::killTimer()停止計時器。

時間事件的優先級很低,如果需要多個計時器,那么跟蹤每一個計時器的ID 是很費時的。這種情況下,較好的方法是為每一個計時器創建一個QTimer 對象。在每一個時間間隔內,QTimer 發出一個 timeout()信號。QTimer 還支持一次性計時器(只發出一次 timeout()信號的計時器)。

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

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

相關文章

【Dify 案例】【MCP實戰】【三】【超級美食家】

接上次的超級助理,我們這一期給出一個超級美食家 首先:我的MCP要申請一個key ` 我們來看看這個MCP服務怎么使用呢。`https://modelscope.cn/mcp/servers/@worryzyy/howtocook-mcp插件里面需要配置 {"mcpServers":{"amap-amap-sse":{"url":&qu…

4.文件管理(文本、日志、Excel表)

目錄 1.文本 2.日志 3.Excel表 1.文本 using System.Text;namespace (自己創建的一個類) {/// <summary>/// 配置文件*.ini讀寫器。/// </summary>public class IniFile{[System.Runtime.InteropServices.DllImport("kernel32")]private static ex…

Java 包裝類詳解

什么是包裝類 Java包裝類&#xff08;Wrapper Classes&#xff09;是將8種基本數據類型封裝成對象的類&#xff0c;位于java.lang包中。每個基本數據類型都有對應的包裝類&#xff1a; byte → Byteshort → Shortint → Integerlong → Longfloat → Floatdouble → Doublec…

阿里云ACP認證-數據倉庫

數據倉庫 Kappa架構&#xff1a;將實時和離線代碼統一&#xff08;優化lambda架構&#xff09;&#xff0c;但是不好修正數據&#xff0c;開發周期長&#xff0c;成本浪費&#xff0c;對于歷史數據的高吞吐量力不從心 原一代數據倉庫&#xff1a; 離線&#xff1a;hivemaxcom…

WebRTC(五):TURN協議

TURN&#xff08;Traversal Using Relays around NAT&#xff09;協議是一個網絡協議&#xff0c;旨在解決 NAT&#xff08;網絡地址轉換&#xff09;和防火墻 環境下的 UDP/TCP通信問題。它通常與 STUN 和 ICE 協議一起使用&#xff0c;廣泛應用于 WebRTC、SIP 和視頻會議等實…

Python 的內置函數 hasattr

Python 內建函數列表 > Python 的內置函數 hasattr Python 的內置函數 hasattr() 用于檢查一個對象是否具有指定的屬性或方法。該函數的語法為&#xff1a; hasattr(object, name)參數說明&#xff1a; object&#xff1a;要檢查的對象&#xff0c;可以是任何 Python 對象…

docker使用技巧之把擴展卷命名變成有意義

背景 之前使用別人的鏡像之后&#xff0c;啟動docker后發出現了一堆看不懂名稱的擴展卷 eg&#xff1a;集群查看 擴展卷查看 這個時候如果有很多集群需要清理擴展卷就很麻煩&#xff0c;不知道是哪個集群的 操作步驟 可以實現的分析&#xff1a;這個擴展卷的信息應該是和…

《博物通書》《博物新編》與滿清歷史篡改

《博物新編》作為近代西方科技輸入中國的首部著作&#xff0c;其問世猶如一顆投入平靜湖面的巨石&#xff0c;在 19 世紀中期的中國激起層層漣漪&#xff0c;對中國近代科學發展產生了多維度、深層次的影響。它不僅是知識傳播的載體&#xff0c;更是推動中國科學從傳統走向近代…

【入門】【例18.1】 睡眠

| 時間限制&#xff1a;C/C 1000MS&#xff0c;其他語言 2000MS 內存限制&#xff1a;C/C 64MB&#xff0c;其他語言 128MB 難度&#xff1a;中等 分數&#xff1a;100 OI排行榜得分&#xff1a;12(0.1分數2難度) 出題人&#xff1a;root | 描述 一個人只有每天睡眠時間到達 8…

DAY 38 Dataset和Dataloader類

知識點回顧&#xff1a; Dataset類的__getitem__和__len__方法&#xff08;本質是python的特殊方法&#xff09;Dataloader類minist手寫數據集的了解 作業&#xff1a;了解下cifar數據集&#xff0c;嘗試獲取其中一張圖片 import torch import torch.nn as nn import torch.o…

【Kubernetes】以LOL的視角打開K8s

前言 對于大部分后端程序員乃至于非后端程序員來說&#xff0c;在當前的云原生時代&#xff0c;Kubernetes&#xff08;后稱K8s&#xff09;都是繞不開的一項技術&#xff1b;同時&#xff0c;對于這個時代的程序員來說&#xff0c;“英雄聯盟”&#xff08;后稱LOL&#xff0…

UE5 游戲模板 —— FirstShootGame

UE5 游戲模板 —— FirstShootGame 前言一、GameMode二、組件1.ShooterPickUpComponent單播多播 2.ShooterWeaponComponent附著武器開火 3.小結4.ShooterProjectile初始化碰撞受擊檢測 三、Character初始化輸入移動 總結 前言 有了前兩個俯視角游戲的基礎讓我們來看看相對復雜…

國家級與省級(不含港澳臺)標準地圖服務網站匯總

在先前的文章中&#xff0c;介紹了部分省級的標準地圖服務網站可以下載各個區縣近幾年、不同要素的標準地圖&#xff08;鏈接&#xff1a;國家與省市縣 標準地圖服務網站 審圖號地圖下載&#xff09;&#xff0c;但是當時只匯總了部分省級的標準地圖服務網站。 這兩天看到了一個…

前端開發面試題總結-vue3框架篇(一)

文章目錄 Vue3高頻問答一、vue2/vue3中常用的構建工具和腳手架分別是什么? 有什么區別?二、請說一說vue2和vue3的區別&#xff1f;三、請說一說vue2和vue3響應式原理的區別&#xff1f;四、vue3 如何定義響應式數據?五、說一說你對vue3中的setup函數?六、說一說vue3中的路由…

【LLM06---相對位置編碼】

文章目錄 相對位置編碼經典式XLNET式T5式DeBERTa式 相對位置編碼 上一節我們介紹了絕對位置編碼&#xff0c;這一節我們來看相對位置編碼&#xff0c;也就是用相對位置信息來表示&#xff0c;之前每一個token的位置式通過一個絕對的位置向量來表示的&#xff0c;現在我們在計算…

純跟蹤算法本質解密:航向角偏差=預瞄角?數學證明與工程實踐

定義關鍵問題 在深入純跟蹤算法核心前&#xff0c;必須澄清一對容易被混淆但至關重要的概念&#xff1a; 概念坐標系物理意義計算方式航向角偏差(α_global)全局坐標系車輛航向與預瞄點方向的夾角預瞄點方位角 - 車輛航向角預瞄角(α_body)車身坐標系預瞄點相對于車輛縱軸的夾…

自動駕駛叉車在倉庫環境中是否安全?

隨著自動駕駛叉車的興起&#xff0c;倉庫運營持續演進。叉車自動化技術的引入使倉庫設施變得更快、更安全且更具成本效益。然而一個關鍵問題依然存在&#xff1a;它們在繁忙的倉庫環境中是否安全&#xff1f; 一 、什么是自動駕駛叉車&#xff1f; 自動駕駛叉車&#xff0c;也…

Neo4j操作指南:修改節點數據與新增節點屬性

Neo4j操作指南&#xff1a;修改節點數據與新增節點屬性 引言 Neo4j作為領先的圖數據庫&#xff0c;提供了靈活的數據操作方式。在實際應用中&#xff0c;我們經常需要修改已有節點的數據或為節點添加新屬性。本文將詳細介紹如何使用Cypher查詢語言在Neo4j中完成這些操作&…

AI大模型學習之基礎數學:微積分在AI大模型中的核心-梯度與優化(梯度下降)詳解

微積分在AI大模型中的核心:梯度與優化(梯度下降) 人工智能(AI)大模型的訓練和優化依賴于數學基礎,其中微積分、線性代數和概率統計構成了其理論核心。微積分在AI中的核心作用在于提供優化工具,尤其是通過梯度和梯度下降方法,幫助模型在高維參數空間中找到損失函數的最…

記錄tweenjs踩坑

初次上手tweenjs&#xff0c;試了很多示例代碼都不生效&#xff0c;結果在html中生效&#xff0c;在vue3的項目中怎么都不生效 <!DOCTYPE html> <html lang"en"><head><title>Tween.js / simplest possible example!</title><meta…