#(19):事件的接受與忽略
版本:
1. 2012-09-29
2. 2013-04-23 更新有關`accept()`和`ignore()`函數的相關內容。
3. 2013-12-02?增加有關`accept()`和`ignore()`函數的示例。
上一章我們介紹了有關事件的相關內容。我們曾經提到,事件可以依情況接受和忽略。現在,我們就來了解下有關事件的更多的知識。
首先來看一段代碼:
~~~
//!!! 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`類添加一個事件函數:
~~~
// CustomButton
...
protected:
void mousePressEvent(QMouseEvent *event);
...
// ---------- custombutton.cpp ---------- //
...
void CustomButton::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
qDebug() << "left";
} else {
QPushButton::mousePressEvent(event);
}
}
...
~~~
我們重寫了`CustomButton`的`mousePressEvent()`函數,也就是鼠標按下。在這個函數中,我們判斷如果鼠標按下的是左鍵,則打印出來“left”字符串,否則,調用父類的同名函數。編譯運行這段代碼,當我們點擊按鈕時,“You clicked this!”字符串不再出現,只有一個“left”。也就是說,我們把父類的實現覆蓋掉了。由此可以看出,父類`QPushButton`的`mousePressEvent()`函數中肯定發出了`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`的默認實現。這一點我們前面已經說明。下面可以從代碼級別來理解這一點,我們可以查看一下`QWidget`的`mousePressEvent()`函數的實現:
~~~
//!!! 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`判斷的那段代碼就不會被執行,因此可能會出現默認其妙的怪異現象。
針對`accept()`和`ignore()`,我們再來看一個例子:
~~~
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`,里面有兩個按鈕對象:`CustomButton`和`CustomButtonEx`。每一個類都重寫了`mousePressEvent()`函數。運行程序點擊 CustomButtonEx,結果是
~~~
CustomButtonEx
~~~
這是因為我們重寫了鼠標按下的事件,但是并沒有調用父類函數或者顯式設置`accept()`或`ignore()`。下面我們在`CustomButtonEx`的`mousePressEvent()`第一行增加一句`event->accept()`,重新運行,發現結果不變。正如我們前面所說,`QEvent`默認是`accept`的,調用這個函數并沒有什么區別。然后我們將`CustomButtonEx`的`event->accept()`改成`event->ignore()`。這次運行結果是
~~~
CustomButtonEx
CustomWidget
~~~
`ignore()`說明我們想讓事件繼續傳播,于是`CustomButtonEx`的父組件`CustomWidget`也收到了這個事件,所以輸出了自己的結果。同理,`CustomWidget`又沒有調用父類函數或者顯式設置`accept()`或`ignore()`,所以事件傳播就此打住。這里值得注意的是,`CustomButtonEx`的事件傳播給了父組件`CustomWidget`,而不是它的父類`CustomButton`。**事件的傳播是在組件層次上面的,而不是依靠類繼承機制。**
接下來我們繼續測試,在`CustomWidget`的`mousePressEvent()`中增加`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”,則接受關閉事件,這個事件所在的操作就是關閉窗口。因此,一旦接受事件,窗口就會被關閉;否則窗口繼續保留。當然,如果窗口內容沒有被修改,則直接接受事件,關閉窗口。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)