Qt事件與常用事件處理、過濾


轉載:

  https://blog.csdn.net/apollon_krj/article/category/6939539

  https://blog.csdn.net/qq_41072190/article/category/7593738

 

在Qt中我們可以應用信號與槽對一些鼠標點擊的操作進行處理,如: 
QPushbutton::clicked 
QPushbutton::realsead 
QPushbutton::pressed 
而信號與槽的處理屬於事件的一種,產生一個信號可以認為是一個信號事件,而槽函數就是對於該信號事件進行處理的回調函數。由於信號與槽屬於事件,也就是說信號很強大,但是事件更強大。那么我們就有必要好好總結一下Qt的常用的一些事件了。

1、首先明確事件處理過程: 
事件(event)是由系統或者Qt本身在不同時刻發出的。當用戶按下鼠標、敲下鍵盤,或者其它情況時候都會發出一個相應的事件。一些事件在對用戶操作做出相應時發出,如鍵盤事件等;另外一些則是由系統自動發出,如計時事件等。Qt程序需要在main()函數創建一個QApplication對象,然后調用它的exec()函數。這個函數就是開始Qt的事件循環。在執行exec()函數之后,程序將進入事件循環來監聽應用程序的事件,當事件發生時,Qt將創建一個事件對象。Qt中所有事件類都繼承自QEvent。在事件對象創建完畢之后,Qt將這個事件對象傳遞給QObject的event()函數。event()函數並不直接處理事件,而是按照事件對象的類型分派給指定的事件處理函數(event handler)進行處理。

2、常用事件(事件處理函數):

 1 keyPressEvent()             //鍵盤按下事件
 2 keyReleaseEvent()           //鍵盤釋放事件
 3 mouseDoubleClickEvent()     //鼠標雙擊事件
 4 mouseMoveEvent()            //鼠標移動事件
 5 mousePressEvent()           //鼠標按下事件
 6 mouseReleaseEvent()         //鼠標釋放事件
 7 timerEvent()                //定時器事件
 8 dragEnterEvent()            //拖拽進入當前窗口事件
 9 dragLeaveEvent()            //拖拽離開當前窗口事件
10 dragMoveEvent()             //拖拽移動事件
11 enterEvent()                //進入窗口區域事件
12 leaveEvent()                //離開窗口區域事件
13 closeEvent()                //關閉窗口事件

以上的事件是比較常用的一些事件。這些事件的回調函數都是虛函數,其成員屬性為”protected function”,在其基類中聲明,再到具體的派生類中進行父類虛函數的覆寫,以實現不同類中對於同一事件的不同處理,以上的虛函數在QWidget中基本都已聲明,我們在具體使用時只需要繼承QWidget,然后在QWidget的派生類中具體實現即可。

3、QTimerEvent定時器事件: 
測試main()函數不變(基類QWidget,MyWidget繼承自QWidget):

#include "mywidget.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyWidget w;
    w.show();
 
    return a.exec();
}

比如,我們采用timerEvent()實現兩個計數器:其中一個計數器的事件觸發的時間間隔為1s(計數到23s停止),另一個為0.5s(一直計數不停止)。分別顯示在不同Label上。

/*myWidget.h*/
#ifndef MYWIDGET_H
#define MYWIDGET_H
 
#include <QWidget>
 
namespace Ui {
class MyWidget;
}
 
class MyWidget : public QWidget
{
    Q_OBJECT
public:
    explicit MyWidget(QWidget *parent = 0);
    ~MyWidget();
protected:
    //定時器事件
    void timerEvent(QTimerEvent *);
private:
    Ui::MyWidget *ui;
    int timerID_fast;        //區分不同定時器,類似於文件描述符
    int timerID_slow;
};
 
#endif // MYWIDGET_H

 

/*myWidget.cpp*/
#include "mywidget.h"
#include "ui_mywidget.h"
 
MyWidget::MyWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MyWidget)
{
    ui->setupUi(this);
    timerID_fast = this->startTimer(1000);//以毫秒為單位,每隔1秒觸發一次定時器
    timerID_slow = this->startTimer(500);
}
void MyWidget::timerEvent(QTimerEvent *ev)
{
//如果觸發定時器的是編號為timerID_fast,則處理,否則處理timerID_slow對應的代碼
    if(ev->timerId() == timerID_fast){
        static int sec = 0;
        if(sec == 20){  //定時到20s時停止計時
            killTimer(timerID_fast);
        }
        ui->labelCoordinate->setText(QString("<center><h1>Timer out:%1</h1></center>").arg(sec++));
        //<center><h1>Timer out:%1</h1></center>為HTML的寫法,中間(center)標題大小(h1)顯示
    }
    else if(ev->timerId() == timerID_slow){
        static int sec = 0;
        ui->label->setText(QString("<center><h1>Timer out:%1</h1></center>").arg(sec++));
    }
}
MyWidget::~MyWidget()
{
    delete ui;
}

結果如下:

對於定時器的操作就是設定了每個一段時間產生一個中斷,然后去執行我們在派生類中重新覆寫的虛函數(這里的虛函數是覆寫基類中的,所以其返回值、參數、函數名都和基類的函數名是相同的),顯然這是一個回調函數。

4、QMouseEvent鼠標事件: 
鼠標事件應該算得上是GUI編程中用的最多的一個事件了,比如掃雷、棋類游戲等都對於鼠標事件的使用是相當頻繁的。而鼠標事件我們就拿press、release、move、enter、leave等幾個事件來說說:

QWidget.cpp以及QWidget.h必須要修改,保持創建QWidget時即可,添加Class文件為MyLabel。在標簽上顯示鼠標move的坐標、鼠標位於標簽內還是標簽外(當鼠標位於標簽內時,會顯示move的坐標,所以測試鼠標leave與enter時應注銷move的setText()),在qDebug中顯示鼠標按鍵是左鍵/右鍵/中鍵,

/*myLabel.h*/
#ifndef MYLABEL_H
#define MYLABEL_H
//需要對標簽進行提升,提升原本的類QLabel為MyLabel
#include <QWidget>
#include <QLabel>
class MyLabel : public QLabel
{
    Q_OBJECT
public:
    explicit MyLabel(QWidget *parent = 0);
protected:
    //鼠標點擊事件
    void mousePressEvent(QMouseEvent *ev);
    //鼠標釋放事件
    void mouseReleaseEvent(QMouseEvent *ev);
    //鼠標移動事件
    void mouseMoveEvent(QMouseEvent *ev);
    //進入窗口(Label)區域
    void enterEvent(QEvent *);
    //離開窗口(Label)區域
    void leaveEvent(QEvent *);
signals:
 
public slots:
};
 
#endif // MYLABEL_H

 

/*myLabel.cpp*/
#include "mylabel.h"
#include <QMouseEvent>
#include <qDebug>
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
    //設定追蹤鼠標,一開始運行就追蹤而不是需要先按下鍵才會追蹤
    this->setMouseTracking(true);
}
 
//鼠標按下事件處理函數
void MyLabel::mousePressEvent(QMouseEvent *ev)
{
    if(ev->button() == Qt::LeftButton){
        qDebug() << "Left";
    }else if(ev->button() == Qt::RightButton){
        qDebug() << "Right";
    }else if(ev->button() == Qt::MidButton){
        qDebug() << "Middle";
    }
    /*
     * QString str = QString("abc %1 ^_^ %2").arg(123456).arg("ABCDEF");
     * str = abc 123456 ^_^ ABCDEF
    */
    int i = ev->x();
    int j = ev->y();
    QString str = QString("<center><h1>Mouse Press:(%1, %2)</h1></center>").arg(i).arg(j);
    this->setText(str);
}
//鼠標抬起事件處理函數
void MyLabel::mouseReleaseEvent(QMouseEvent *ev)
{
    QString str = QString("<center><h1>Mouse Release:(%1, %2)</h1></center>").arg(ev->x()).arg(ev->y());
    this->setText(str);
}
//鼠標移動事件處理函數
void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
    QString str = QString("<center><h1>Mouse Move:(%1, %2)</h1></center>").arg(ev->x()).arg(ev->y());
    //this->setText(str);
}
//鼠標位於標簽內
void MyLabel::enterEvent(QEvent * ev)
{
    QString str = QString("<center><h1>Mouse Enter</h1></center>");
    this->setText(str);
}
//鼠標位於標簽外
void MyLabel::leaveEvent(QEvent *ev)
{
    QString str = QString("<center><h1>Mouse Leave</h1></center>");
    this->setText(str);
}

5、QCloseEvent事件與QMessageBox使用: 
在上例中QWidget加上鼠標關閉事件,點擊右上角關閉彈出QMessageBox::question窗口選擇是否關閉,該功能用到的accept()與ignore()兩個事件處理函數,第一個accept()是接收事件,之后事件就不再向下傳遞了,而ignore()則是忽略事件不做處理事件會傳遞給父組件(而不是基類)

void MyWidget::closeEvent(QCloseEvent *ev)
{
    int ret = QMessageBox::question(this,"question","Close the Windows?");
    if(ret == QMessageBox::Yes){
        //關閉窗口
        //接收事件,事件不再向下傳遞
        ev->accept();
    }else{
        //不關閉窗口
        //忽略事件,事件繼續給父組件傳遞,由於父組件沒有做關於close的接收操作
        //所以不做關閉操作
        ev->ignore();
    }
}

6、QKeyEvent鍵盤事件: 
ev作為傳遞事件的參數其參數key()可以獲取引發事件(中斷)的是哪一個按鍵,與Qt的枚舉變量進行比對,來進行相應的操作:

void MyWidget::keyPressEvent(QKeyEvent *ev)
{
    if(ev->key() >= Qt::Key_A && ev->key() <= Qt::Key_Z){
        qDebug() << (char)ev->key();
    }
    else{
        qDebug() << ev->key();
    }
}

該鍵盤事件處理函數是對A~Z的字母進行識別並打印(不打印ASCII碼,而打印對應字符),而對於非A~Z的字符則打印ASCII碼: 

常用的一些鍵的枚舉常量如下所示(我們不必要去記憶這些東西,只需要在幫助文檔查一下即可,但是還是有必要瀏覽一遍的):

Qt::Key_Escape  0x01000000   
Qt::Key_Tab 0x01000001   
Qt::Key_Backtab 0x01000002   
Qt::Key_Backspace   0x01000003   
Qt::Key_Return  0x01000004   
Qt::Key_Enter   0x01000005  Typically located on the keypad.
Qt::Key_Insert  0x01000006   
Qt::Key_Delete  0x01000007   
Qt::Key_Pause   0x01000008  The Pause/Break key (Note: Not related to pausing media)
Qt::Key_Print   0x01000009   
Qt::Key_SysReq  0x0100000a   
Qt::Key_Clear   0x0100000b   
Qt::Key_Home    0x01000010   
Qt::Key_End 0x01000011   
Qt::Key_Left    0x01000012   
Qt::Key_Up  0x01000013   
Qt::Key_Right   0x01000014   
Qt::Key_Down    0x01000015   
Qt::Key_PageUp  0x01000016   
Qt::Key_PageDown    0x01000017   
Qt::Key_Shift   0x01000020   
Qt::Key_Control 0x01000021  On OS X, this corresponds to the Command keys.
Qt::Key_Meta    0x01000022  On OS X, this corresponds to the Control keys. On Windows keyboards, this key is mapped to the Windows key.
Qt::Key_Alt 0x01000023   
Qt::Key_AltGr   0x01001103  On Windows, when the KeyDown event for this key is sent, the Ctrl+Alt modifiers are also set.
Qt::Key_CapsLock    0x01000024   
Qt::Key_NumLock 0x01000025   
Qt::Key_ScrollLock  0x01000026   
Qt::Key_F1  0x01000030   
Qt::Key_F2  0x01000031   
Qt::Key_F3  0x01000032   
Qt::Key_F4  0x01000033   
Qt::Key_F5  0x01000034   
Qt::Key_F6  0x01000035   
Qt::Key_F7  0x01000036   
Qt::Key_F8  0x01000037   
Qt::Key_F9  0x01000038   
Qt::Key_F10 0x01000039   
Qt::Key_F11 0x0100003a   
Qt::Key_F12 0x0100003b   
 
Qt::Key_0   0x30     
Qt::Key_1   0x31     
Qt::Key_2   0x32     
Qt::Key_3   0x33     
Qt::Key_4   0x34     
Qt::Key_5   0x35     
Qt::Key_6   0x36     
Qt::Key_7   0x37     
Qt::Key_8   0x38     
Qt::Key_9   0x39
 
Qt::Key_A   0x41     
Qt::Key_B   0x42     
Qt::Key_C   0x43     
Qt::Key_D   0x44     
Qt::Key_E   0x45     
Qt::Key_F   0x46     
Qt::Key_G   0x47     
Qt::Key_H   0x48     
Qt::Key_I   0x49     
Qt::Key_J   0x4a     
Qt::Key_K   0x4b     
Qt::Key_L   0x4c     
Qt::Key_M   0x4d     
Qt::Key_N   0x4e     
Qt::Key_O   0x4f     
Qt::Key_P   0x50     
Qt::Key_Q   0x51     
Qt::Key_R   0x52     
Qt::Key_S   0x53     
Qt::Key_T   0x54     
Qt::Key_U   0x55     
Qt::Key_V   0x56     
Qt::Key_W   0x57     
Qt::Key_X   0x58     
Qt::Key_Y   0x59     
Qt::Key_Z   0x5a

 

7、event()事件處理器與eventFilter()事件過濾器: 這兩個函數也是虛函數,均聲明在基類QObject中。

 1 virtual bool event(QEvent * e)

2 virtual bool eventFilter(QObject * watched, QEvent * event) 

有時候,我們需要對某個事件進行屏蔽,可以通過在其事件處理的中斷函數中進行操作,也可以在event()事件處理函數中進行操作,還可以在eventFilter()事件過濾器中進行操作,以上三種操作:eventFilter()事件過濾器中操作是比較方便的,可以說是指哪打哪,可以隨意過濾掉某一個類中的某一個事件。而event()也可以做到,但是event()函數主要功能並不是直接處理事件,而是按照事件對象的類型分派給指定的事件處理函數(event handler)進行處理。 
這種關系可以表示為: 

event()是一個根據不同事件做分類處理的功能,具體如下(就類似於switch…case語句或者if..else..語句),這是對原有event()虛函數的覆寫(屏蔽定時器,屏蔽除了Y按鍵的其它按鍵):

//事件處理器event()只在本控件有效,我們這里通過event()進行事件過濾
bool MyWidget::event(QEvent *ev)
{
//switch的寫法
    //對於定時器進行功能進行覆寫,對鍵盤的Y鍵保留中斷,其他鍵忽略。其它時間仍按默認處理
    //默認的是在event()的分發層(下一級)進行處理,而定時器的處理直接放在了event()函數中處理
    //到了下一層定時器的處理發現return的是true就不再處理,鍵盤按鍵也是同理
    switch(ev->type()){
        case QEvent::Timer: //如果是定時器事件,返回true停止時間傳播,忽略定時器中斷
            //timerEvent(static_cast<QTimerEvent*>(ev));       //不處理直接返回就是干掉了定時器
            //取消注釋,則是將定時器事件提到event()函數中直接處理
            return true;
            break;
        case QEvent::KeyPress:
            if(static_cast<QKeyEvent *>(ev)->key() == Qt::Key_Y){
                return QWidget::event(ev);
            }
            return true;
            break;
        default:
        //默認的則是保留原有event()的處理方式
            return QWidget::event(ev);
            break;
    }
}

當然,用switch能寫出來用if…else…也可以,應根據需求進行選擇(當分支較多時switch是一個空間換取時間的做法(switch是隨機訪問會為每個case的指令塊生成一個起始地址標記,自然存在一個跳躍表的空間),而if…else…是一個時間效率不及switch的做法,但節省空間。而一般要用event屏蔽事件分支都比較少,二者就無太大區別)。關於if…else…與switch()case的效率比較參考blog:switch與ifelse的效率問題

//if...else...的寫法
bool MyWidget::event(QEvent *ev)
{
    if(ev->type() == QEvent::Timer){        //定時器特殊處理:忽略
        return true;
    }
    else if(ev->type() == QEvent::KeyPress){    //鍵盤處理:保留Y鍵,其它鍵忽略
        if(static_cast<QKeyEvent *>(ev)->key() == Qt::Key_Y){
            return QWidget::event(ev);
        }
        return true;
    }
    else{
        return QWidget::event(ev);  //其它事件按原有方式處理
        //注意這里一定要把不特殊處理的按原有方式處理
    }
}

除了在event()向每個具體的類對象分發事件以前,我們可以用event()進行事件的提前處理,而到達底層具體事件處理時,先查看event()對事件的返回值的狀態(true/false),再確定處理方式。而在event()之上,我們也可以通過eventFilter()來完成事件的過濾(提前處理),因為這(eventFilter())就是事件過濾器。說到底無論是事件分發還是事件過濾,都是對事件的提前處理,並向底層返回一個處理狀態。只是如果多個類中要對同一事件進行相同的提前處理,需要再多個類中實現多次event()的覆寫,eventFilter()則需要一次即可。下例中是對label標簽頁的事件提前處理/事件過濾:

//ui->label->installEventFilter(this);//在構造函數中需要安裝過濾器,這是不同於event的一個地方
//對於不同的類要分別安裝過濾器
bool MyWidget::eventFilter(QObject *obj, QEvent *ev)
{
    //對標簽label進行事件過濾
    if(obj == ui->label){
        QMouseEvent * env = static_cast<QMouseEvent *>(ev);
        if(ev->type() == QEvent::MouseMove){
            ui->label->setText(QString("<center><h1>Mouse Move:(%1, %2)"
                                       "</h1></center>").arg(env->x()).arg(env->y()));
            return true;    //結束傳播
        }
        else if(ev->type() == QEvent::MouseButtonPress){
            ui->label->setText(QString("<center><h1>Mouse pressed</h1></center>"));
            return true;    //結束傳播
        }
        else if(ev->type() == QEvent::MouseButtonRelease){
            ui->label->setText(QString("<center><h1>Mouse released</h1></center>"));
            return true;    //結束傳播
        }
    }
    //其余的事件按照默認(未覆寫)的處理方式處理
        return QWidget::eventFilter(obj,ev);
}

注意:event()、eventFilter()雖然可以用來屏蔽某些事件,但是我們一般不會去修改這兩個函數,而是直接在具體的事件處理函數中進行處理操作。

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM