找回密码
 立即注册
收起左侧

Qt学习之路第19篇 标准对话框 事件的接受与忽略

14
回复
23065
查看
[复制链接]
累计签到:3 天
连续签到:1 天
来源: 2013-9-9 13:53:16 显示全部楼层 |阅读模式

马上注册,查看详细内容!注册请先查看:注册须知

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
版权声明

该文章原创于Qter开源社区(www.qter.org),作者devbean,博客www.devbean.net,转载请注明出处!



版本:



  • 2012-09-29
  • 2013-04-23 更新有关 accept() 和 ignore() 函数的相关内容。

上一章我们介绍了有关事件的相关内容。我们曾经提到,事件可以依情况接受和忽略。现在,我们就来了解下有关事件的更多的知识。


首先来看一段代码:

  1. //!!! Qt5
  2. // ---------- custombutton.h ---------- //
  3. class CustomButton : public QPushButton
  4. {
  5.     Q_OBJECT
  6. public:
  7.     CustomButton(QWidget *parent = 0);
  8. private:
  9.     void onButtonCliecked();
  10. };

  11. // ---------- custombutton.cpp ---------- //
  12. CustomButton::CustomButton(QWidget *parent) :
  13.     QPushButton(parent)
  14. {
  15.     connect(this, &CustomButton::clicked,
  16.             this, &CustomButton::onButtonCliecked);
  17. }

  18. void CustomButton::onButtonCliecked()
  19. {
  20.     qDebug() << "You clicked this!";
  21. }

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

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

  29.     return a.exec();
  30. }
复制代码

这是一段简单的代码,经过我们前面一段时间的学习,我们已经能够知道这段代码的运行结果:点击按钮,会在控制台打印出“You clicked this!”字符串。这是我们前面介绍过的内容。下面,我们向 CustomButton 类添加一个事件函数:

  1. // CustomButton
  2. ...
  3. protected:
  4.     void mousePressEvent(QMouseEvent *event);
  5. ...

  6. // ---------- custombutton.cpp ---------- //
  7. ...
  8. void CustomButton::mousePressEvent(QMouseEvent *event)
  9. {
  10.     if (event->button() == Qt::LeftButton) {
  11.         qDebug() << "left";
  12.     } else {
  13.         QPushButton::mousePressEvent(event);
  14.     }
  15. }
  16. ...
复制代码

我们重写了 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() 函数的实现:

  1. //!!! Qt5
  2. void QWidget::mousePressEvent(QMouseEvent *event)
  3. {
  4.     event->ignore();
  5.     if ((windowType() == Qt::Popup)) {
  6.         event->accept();
  7.         QWidget* w;
  8.         while ((w = QApplication::activePopupWidget()) && w != this){
  9.             w->close();
  10.             if (QApplication::activePopupWidget() == w)
  11.                 w->hide(); // hide at least
  12.         }
  13.         if (!rect().contains(event->pos())){
  14.             close();
  15.         }
  16.     }
  17. }
复制代码

这段代码在 Qt4 和 Qt5 中基本一致(区别在于 activePopupWidget() 一行,Qt4 的版本是 qApp->activePopupWidget())。注意函数的第一个语句:event->ignore();,如果子类都没有重写这个函数,Qt 会默认忽略这个事件,不会继续传播。如果我们在子类的 mousePressEvent() 函数中直接调用了 accept() 或者 ignore(),而没有调用父类的同名函数,QWidget::mousePressEvent() 函数中关于 Popup 判断的那段代码就不会被执行,因此可能会出现默认其妙的怪异现象。


不过,事情也不是绝对的。在一个情形下,我们必须使用 accept() 和 ignore() 函数,那就是窗口关闭的事件。注意,不要试图用前面了解到的有关 accept() 和 ignore() 的知识来理解这个事件!对于 QCloseEvent 事件,调用 accept() 意味着 Qt 会停止事件的传播,调用 ignore() 则意味着事件继续传播。这与前面所说的正好相反。回到我们前面写的简单的文本编辑器。我们在构造函数中添加如下代码:

  1. //!!! Qt5
  2. ...
  3. textEdit = new QTextEdit(this);
  4. setCentralWidget(textEdit);
  5. connect(textEdit, &QTextEdit::textChanged, [=]() {
  6.     this->setWindowModified(true);
  7. });

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

  10. void MainWindow::closeEvent(QCloseEvent *event)
  11. {
  12.     if (isWindowModified()) {
  13.         bool exit = QMessageBox::question(this,
  14.                                       tr("Quit"),
  15.                                       tr("Are you sure to quit this application?"),
  16.                                       QMessageBox::Yes | QMessageBox::No,
  17.                                       QMessageBox::No) == QMessageBox::Yes;
  18.         if (exit) {
  19.             event->accept();
  20.         } else {
  21.             event->ignore();
  22.         }
  23.     } else {
  24.         event->accept();
  25.     }
  26. }
复制代码

setWindowTitle() 函数可以使用

这种语法来表明,在窗口内容发生改变时(通过 setWindowModified(true) 函数通知),Qt 会自动在标题上面的 位置替换成 * 号。我们使用 Lambda 表达式连接 QTextEdit::textChanged() 信号,将 windowModified 设置为 true。

然后我们需要重写 closeEvent() 函数。在这个函数中,我们首先判断是不是有过修改,如果有,则弹出询问框,问一下是否要退出。如果用户点击了“Yes”,则接受关闭事件,这个事件所在的操作就是关闭窗口。因此,一旦接受事件,窗口就会被关闭;否则窗口继续保留。当然,如果窗口内容没有被修改,则直接接受事件,关闭窗口。






回复

使用道具 举报

累计签到:1 天
连续签到:1 天
2014-11-12 10:34:32 显示全部楼层
这一篇感觉没怎么看明白。
回复 支持 2 反对 0

使用道具 举报

累计签到:38 天
连续签到:1 天
2014-1-20 08:26:50 显示全部楼层
不懂啊!!!请问有讲解Qt的ppt吗?
回复 支持 反对

使用道具 举报

累计签到:38 天
连续签到:1 天
2014-1-21 10:15:24 显示全部楼层
您好,请问这段代码具体什么意思,看了还是不懂啊?
    if ((windowType() == Qt:opup)) {
        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();
        }
    }
回复 支持 反对

使用道具 举报

累计签到:1 天
连续签到:1 天
2014-11-13 09:29:04 显示全部楼层
完全不明白是在讲什么啊。。
回复 支持 反对

使用道具 举报

累计签到:5 天
连续签到:1 天
2015-6-17 14:45:48 显示全部楼层
好乱,没懂,这篇pass~~
回复 支持 反对

使用道具 举报

累计签到:6 天
连续签到:1 天
2015-9-9 15:34:48 显示全部楼层
可以在子类中重写这些函数,来实现自己的功能。如果重写的子类没有处理消息,就会自动向其父类传递。
有点乱…………
回复 支持 反对

使用道具 举报

累计签到:3 天
连续签到:1 天
2015-11-15 20:53:29 显示全部楼层
这一篇确实写得云里雾里的,感觉跳了好几个知识点,有几个情况不太明白~
回复 支持 反对

使用道具 举报

累计签到:109 天
连续签到:1 天
2016-1-11 22:07:25 显示全部楼层
最后一段代码 第5-7行:
connect(textEdit, &QTextEdit::textChanged, [=]() {  this->setWindowModified(true); });

请问下lambda表达式里 捕获列表 里的 “=”是什么意思?为什么是“=”呢?
回复 支持 反对

使用道具 举报

累计签到:20 天
连续签到:1 天
2016-5-30 23:54:30 显示全部楼层
中间的内容跨度有点大,希望楼主有空再补充完善一下,让我们看的更明白一些
回复 支持 反对

使用道具 举报

累计签到:2 天
连续签到:1 天
2017-3-9 17:51:37 显示全部楼层
silverdemon 发表于 2016-1-11 22:07
最后一段代码 第5-7行:
connect(textEdit, &QTextEdit::textChanged, [=]() {  this->setWindowModified(t ...

=拷贝赋值 &引用  没特指的话就是用到的外部变量都是拷贝的副本
回复 支持 反对

使用道具 举报

累计签到:2 天
连续签到:1 天
2017-3-9 18:05:37 显示全部楼层
额- -意思是不建议使用accept ignore来操作是否让事件停下来,但是除了closeEvent调用意义不同,accept接受事件,事件不继续传递,ignore忽略事件,不操作,事件停止传递,都是停下来,很郁闷,还不如不操作直接调用基类- -事件函数,大家都开心
默认是accept- -,感觉除了closeEvent accept有点用,其他事件函数就很鸡肋
回复 支持 反对

使用道具 举报

累计签到:10 天
连续签到:1 天
2018-2-26 16:20:48 显示全部楼层
这篇与前面一篇和后面一篇关系紧密,这篇如果看不懂,建议把前面一篇和后面一篇结合看一遍,然后看这篇就懂了
回复 支持 反对

使用道具 举报

尚未签到

2018-11-4 09:40:37 显示全部楼层
是不是如果重写父类的函数,则父类的这个函数对应的信号就会被忽略,而去执行重写后的的函数?
回复 支持 反对

使用道具 举报

累计签到:2 天
连续签到:2 天
2018-11-21 14:42:04 显示全部楼层

DevBean Tech World
去作者的一个博客看下,里面比这里详细
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了