上一章我们介绍了有关事件的相关内容。我们曾经提到,事件可以依情况接受和忽略。现在,我们就来了解下有关事件的更多的知识。
首先来看一段代码:
//!!! 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 的事件对象有两个函数:accept()和ignore()。正如它们的名字一样,前者用来告诉 Qt,这个类的事件处理函数想要处理这个事件;后者则告诉 Qt,这个类的事件处理函数不想要处理这个事件 。在事件处理函数中,可以使用isAccepted()来查询这个事件是不是已经被接收了。具体来说:如果一个事件处理函数调用了一个事件对象的accept()函数,这个事件就不会被继续传播给其父组件;如果它调用了事件的ignore()函数,Qt 会从其父组件中寻找另外的接受者。
事实上,我们很少会使用accept()和ignore()函数,而是像上面的示例一样,如果希望忽略事件,只要调用父类的响应函数即可。
针对accept()
和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(); } }
这段代码在一个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。事件的传播是在组件层次上面的,而不是依靠类继承机制。
在一个特殊的情形下,我们必须使用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”,则接受关闭事件,这个事件所在的操作就是关闭窗口。因此,一旦接受事件,窗口就会被关闭;否则窗口继续保留。当然,如果窗口内容没有被修改,则直接接受事件,关闭窗口。