【Qt筆記】事件的接受與忽略

上一章咱們介紹了有關事件的相關內容。咱們曾經提到,事件能夠依狀況接受和忽略。如今,咱們就來了解下有關事件的更多的知識。app

首先來看一段代碼:編輯器

//!!! Qt5
// ---------- custombutton.h ---------- //
class CustomButton : public QPushButton
{
    Q_OBJECT
public:
    CustomButton(QWidget *parent = 0);
private:
    void onButtonCliecked();
};

// ---------- custombutton.cpp ---------- //
CustomButton::CustomButton(QWidget *parent) :
    QPushButton(parent)
{
    connect(this, &CustomButton::clicked,
            this, &CustomButton::onButtonCliecked);
}

void CustomButton::onButtonCliecked()
{
    qDebug() << "You clicked this!";
}

// ---------- main.cpp ---------- //
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    CustomButton btn;
    btn.setText("This is a Button!");
    btn.show();

    return a.exec();
}

這是一段簡單的代碼,通過咱們前面一段時間的學習,咱們已經可以知道這段代碼的運行結果:點擊按鈕,會在控制檯打印出「You clicked this!」字符串。這是咱們前面介紹過的內容。下面,咱們向CustomButton類添加一個事件函數:ide

// CustomButton
...
protected:
    void mousePressEvent(QMouseEvent *event);
...

// ---------- custombutton.cpp ---------- //
...
void CustomButton::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        qDebug() << "left";
    } else {
        QPushButton::mousePressEvent(event);
    }
}
...

咱們重寫了CustomButtonmousePressEvent()函數,也就是鼠標按下。在這個函數中,咱們判斷若是鼠標按下的是左鍵,則打印出來「left」字符串,不然,調用父類的同名函數。編譯運行這段代碼,當咱們點擊按鈕時,「You clicked this!」字符串再也不出現,只有一個「left」。也就是說,咱們把父類的實現覆蓋掉了。由此能夠看出,父類QPushButtonmousePressEvent()函數中確定發出了clicked()信號,不然的話,咱們的槽函數怎麼會不執行了呢?這暗示咱們一個很是重要的細節:當重寫事件回調函數時,時刻注意是否須要經過調用父類的同名函數來確保原有實現仍能進行!好比咱們的CustomButton類,若是像咱們這麼覆蓋函數,clicked()信號永遠不會發生,你鏈接到這個信號的槽函數也就永遠不會被執行。這個錯誤很是隱蔽,極可能會浪費你不少時間才能找到。由於這個錯誤不會有任何提示。這必定程度上說,咱們的組件「忽略」了父類的事件,但這更多的是一種違心之舉,一種錯誤。函數

經過調用父類的同名函數,咱們能夠把 Qt 的事件傳遞當作鏈狀:若是子類沒有處理這個事件,就會繼續向其父類傳遞。Qt 的事件對象有兩個函數:accept()ignore()。正如它們的名字同樣,前者用來告訴 Qt,這個類的事件處理函數想要處理這個事件;後者則告訴 Qt,這個類的事件處理函數不想要處理這個事件。在事件處理函數中,可使用isAccepted()來查詢這個事件是否是已經被接收了。具體來講:若是一個事件處理函數調用了一個事件對象的accept()函數,這個事件就不會被繼續傳播給其父組件;若是它調用了事件的ignore()函數,Qt 會從其父組件中尋找另外的接受者。學習

事實上,咱們不多會使用accept()ignore()函數,而是像上面的示例同樣,若是但願忽略事件(所謂忽略,是指本身不想要這個事件),只要調用父類的響應函數便可。記得咱們曾經說過,Qt 中的事件都是 protected 的,所以,重寫的函數一定存在着其父類中的響應函數,因此,這個方法是可行的。爲何要這麼作,而不是本身去手動調用這兩個函數呢?由於咱們沒法確認父類中的這個處理函數有沒有額外的操做。若是咱們在子類中直接忽略事件,Qt 會去尋找其餘的接收者,該子類的父類的操做會被忽略(由於沒有調用父類的同名函數),這可能會有潛在的危險。爲了不本身去調用accept()ignore()函數,而是儘可能調用父類實現,Qt 作了特殊的設計:事件對象默認是 accept 的,而做爲全部組件的父類QWidget的默認實現則是調用ignore()。這麼一來,若是你本身實現事件處理函數,不調用QWidget的默認實現,你就等因而接受了事件;若是你要忽略事件,只需調用QWidget的默認實現。這一點咱們前面已經說明。下面能夠從代碼級別來理解這一點,咱們能夠查看一下QWidgetmousePressEvent()函數的實現:測試

//!!! Qt5
void QWidget::mousePressEvent(QMouseEvent *event)
{
    event->ignore();
    if ((windowType() == Qt::Popup)) {
        event->accept();
        QWidget* w;
        while ((w = QApplication::activePopupWidget()) && w != this){
            w->close();
            if (QApplication::activePopupWidget() == w)
                w->hide(); // hide at least
        }
        if (!rect().contains(event->pos())){
            close();
        }
    }
}

這段代碼在 Qt4 和 Qt5 中基本一致(區別在於activePopupWidget()一行,Qt4 的版本是qApp->activePopupWidget())。注意函數的第一個語句:event->ignore(),若是子類都沒有重寫這個函數,Qt 會默認忽略這個事件,繼續尋找下一個事件接收者。若是咱們在子類的mousePressEvent()函數中直接調用了accept()或者ignore(),而沒有調用父類的同名函數,QWidget::mousePressEvent()函數中關於Popup判斷的那段代碼就不會被執行,所以可能會出現默認其妙的怪異現象。ui

針對accept()ignore(),咱們再來看一個例子:this

class CustomButton : public QPushButton
{
    Q_OBJECT
public:
    CustomButton::CustomButton(QWidget *parent)
        : QPushButton(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomButton";
    }
};

class CustomButtonEx : public CustomButton
{
    Q_OBJECT
public:
    CustomButtonEx::CustomButtonEx(QWidget *parent)
        : CustomButton(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomButtonEx";
    }
};

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    CustomWidget::CustomWidget(QWidget *parent)
        : QWidget(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomWidget";
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow::MainWindow(QWidget *parent = 0)
        : QMainWindow(parent)
    {
        CustomWidget *widget = new CustomWidget(this);
        CustomButton *cbex = new CustomButton(widget);
        cbex->setText(tr("CustomButton"));
        CustomButtonEx *cb = new CustomButtonEx(widget);
        cb->setText(tr("CustomButtonEx"));
        QVBoxLayout *widgetLayout = new QVBoxLayout(widget);
        widgetLayout->addWidget(cbex);
        widgetLayout->addWidget(cb);
        this->setCentralWidget(widget);
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "MainWindow";
    }
};

這段代碼在一個MainWindow中添加了一個CustomWidget,裏面有兩個按鈕對象:CustomButtonCustomButtonEx。每個類都重寫了mousePressEvent()函數。運行程序點擊 CustomButtonEx,結果是設計

CustomButtonEx

這是由於咱們重寫了鼠標按下的事件,可是並無調用父類函數或者顯式設置accept()ignore()。下面咱們在CustomButtonExmousePressEvent()第一行增長一句event->accept(),從新運行,發現結果不變。正如咱們前面所說,QEvent默認是accept的,調用這個函數並無什麼區別。而後咱們將CustomButtonExevent->accept()改爲event->ignore()。此次運行結果是code

CustomButtonEx
CustomWidget

ignore()說明咱們想讓事件繼續傳播,因而CustomButtonEx的父組件CustomWidget也收到了這個事件,因此輸出了本身的結果。同理,CustomWidget又沒有調用父類函數或者顯式設置accept()ignore(),因此事件傳播就此打住。這裏值得注意的是,CustomButtonEx的事件傳播給了父組件CustomWidget,而不是它的父類CustomButton事件的傳播是在組件層次上面的,而不是依靠類繼承機制。

接下來咱們繼續測試,在CustomWidgetmousePressEvent()中增長QWidget::mousePressEvent(event)。此次的輸出是

CustomButtonEx
CustomWidget
MainWindow

若是你把QWidget::mousePressEvent(event)改爲event->ignore(),結果也是同樣的。這正如咱們前面說的,QWidget的默認是調用event->ignore()

在一個特殊的情形下,咱們必須使用accept()ignore()函數,那就是窗口關閉的事件。對於窗口關閉QCloseEvent事件,調用accept()意味着 Qt 會中止事件的傳播,窗口關閉;調用ignore()則意味着事件繼續傳播,即阻止窗口關閉。回到咱們前面寫的簡單的文本編輯器。咱們在構造函數中添加以下代碼:

//!!! Qt5
...
textEdit = new QTextEdit(this);
setCentralWidget(textEdit);
connect(textEdit, &QTextEdit::textChanged, [=]() {
    this->setWindowModified(true);
});

setWindowTitle("TextPad [*]");
...

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (isWindowModified()) {
        bool exit = QMessageBox::question(this,
                                      tr("Quit"),
                                      tr("Are you sure to quit this application?"),
                                      QMessageBox::Yes | QMessageBox::No,
                                      QMessageBox::No) == QMessageBox::Yes;
        if (exit) {
            event->accept();
        } else {
            event->ignore();
        }
    } else {
        event->accept();
    }
}

setWindowTitle()函數可使用 [ ] 這種語法來代表,在窗口內容發生改變時(經過setWindowModified(true)函數通知),Qt 會自動在標題上面的 [ ] 位置替換成 * 號。咱們使用 Lambda 表達式鏈接QTextEdit::textChanged()信號,將windowModified設置爲 true。而後咱們須要重寫closeEvent()函數。在這個函數中,咱們首先判斷是否是有過修改,若是有,則彈出詢問框,問一下是否要退出。若是用戶點擊了「Yes」,則接受關閉事件,這個事件所在的操做就是關閉窗口。所以,一旦接受事件,窗口就會被關閉;不然窗口繼續保留。固然,若是窗口內容沒有被修改,則直接接受事件,關閉窗口。

相關文章
相關標籤/搜索