`
tomhibolu
  • 浏览: 1413707 次
文章分类
社区版块
存档分类
最新评论

乱谈Qt事件循环嵌套

 
阅读更多
  • 本文旨在说明:QDialog::exec()、QMenu::exec()等开启的局部事件循环,易用的背后,还有很多的陷阱...

引子

Qt 是事件驱动的,基本上,每一个Qt程序我们都会通过QCoreApplication或其派生类的exec()函数来开启事件循环(QEventLoop):

int main(int argc, char**argv)
{
    QApplication a(argc, argv);
    return a.exec(); 
}

但是在同一个线程内,我们可以开启多个事件循环,比如通过:

  • QDialog::exec()
  • QDrag::exec()
  • QMenu::exec()
  • ...

这些东西都很常用,不是么?它们每一个里面都在执行这样的语句:

QEventLoop loop;     //事件循环
loop.exec();

既然是同一线程内,这些显然是无法并行运行的,那么只能是嵌套运行。

如何演示?

如何用最小的例子来直观说明这个问题呢?

利用定时器来演示应该是最方便的。于是,很容易写出来这样的代码:

#include <QtCore>

class Object : public QObject
{
public:
    Object() {startTimer(200); }

protected:
    void timerEvent(QTimerEvent *) {
        static int level = 0;
        qDebug()<<"Enter: <<"++level;
        QEventLoop loop;     //事件循环
        loop.exec();
        qDebug()<<"Leave: "<<level;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Object w;
    return a.exec();
}

然后我们可以期待看到:

Enter: 1
Enter: 2
Enter: 3
...

但是,很让人失望,这并不会工作。因为Qt对Timer事件派发时进行了处理:

  • 如果当前在处理Timer事件,新的Timer将不会被派发。

演示

我们对这个例子进行一点改进:

  • 收到Timer事件后,我们立即post一个自定义事件(然后我们在对自定义事件的相应中开启局部的事件循环)。这样Timer事件的相应可以立即返回,新的Timer事件可以持续被派发。

另外,为了友好一点,使用了 QPlainTextEdit 来显示结果:

#include <QtGui>
#include <QtCore>

class Widget : public  QPlainTextEdit
{
public:
    Widget() {startTimer(200); }

protected:
    bool event(QEvent * evt)
    {
        if (evt->type() == QEvent::Timer) {
            qApp->postEvent(this, new QEvent(QEvent::User));
        } else if (evt->type() == QEvent::User) {
            static int level = 0;
            level++;
            this->appendPlainText(QString("Enter : %1").arg(++level));
            QEventLoop loop;
            loop.exec();
            this->appendPlainText(QString("Leave: %1").arg(level));
        }
        return QPlainTextEdit::event(evt);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

有什么用?

这个例子确实没有什么,因为似乎没人会写这样的代码。

但是,当你调用

  • QDialog::exec()
  • QMenu::exec()
  • QDrag::exec()
  • ...

等函数时,实际上就启动了嵌套的事件循环,而如果不小心的话,还有遇到各种怪异的问题!

== QDialog::exec() vs QDialog::open()==QDialog 模态对话框与事件循环以及漫谈QWidget及其派生类(四)我们解释过QDialog::exec()。它最终就是调用QEventLoop::exec()。

QDialog::exec()这个东西是这么常用,以至于我们很少考虑这个东西的不利因素。QDialog::open()尽管被官方所推荐,但是似乎很少有人用,很多人可能还不知道它的存在。

但是Qt官方blog中:

一文介绍了 exec() 可能造成的危害,并鼓励大家使用 QDialog::open()

在Qt官方的Qt Quarterly中:*QtQuarterly30 之 New Ways of Using Dialogs对QDialog::open()有详细的介绍

QDialog::open()劣势与优势

看个例子:我们通过颜色对话框选择一个颜色,

  • 使用静态函数,写法很简介(内部调用exec()启动事件循环)

void Widget::onXXXClicked()
{
    QColor c = QColorDialog::getColor();
}
  • 对话框的常见用法,使用exec()启动事件循环,很直观

void Widget::onXXXClicked()
{
    QColorDialog dlg(this);
    dlg.exec();
    QColor c = dlg.currentColor();
}
  • 使用open()函数,比较不直观(因为是异步,需要链接一个槽)

void Widget::onXXXClicked()
{
    QColorDialog *dialog = new QColorDialog;
    dialog->open(this, SLOT(dialogClosed(QColor)));
}
void Widget::dialogClosed(const QColor &color)
{
    QColor = color;
}

好处嘛(就摘录Andreas Aardal Hanssen的话吧):

  • By using open() instead of exec(), you need to write a few more lines of code (implementing the target slot). But what you gain is very significant: complete control over execution. Now, the event loop is no longer nested/reentered, you’re not blocking inside a magic exec() function

局部事件循环导致崩溃

Kde开发者官方blog中描述这个问题:

在某个槽函数中,我们通过QDialog::exec() 弹出一个对话框。

void ParentWidget::slotDoSomething() 
{
    SomeDialog dlg( this ); //分配在栈上的对话框
    if (dlg.exec() == QDialog::Accepted ) {
        const QString str = dlg.someUserInput();
        //do something with with str
    }
}

如果这时ParentWidget或者通过其他方式(比如dbus)得到通知,需要被关闭。会怎么样?

程序将崩溃:ParentWidget析构时,将会delete这个对话框,而这个对话框却在栈上。

简单模拟一下(在上面代码中加一句即可):

void ParentWidget::slotDoSomething() 
{
    QTimer::singleShot(1000, this, SLOT(deleteLater()));
...

这篇blog最终给出的结论是:将对话框分配到堆上,并使用QPointer来保存对话框指针。

上面的代码,大概要写成这样:

void ParentWidget::slotDoSomething() 
{
    QWeakPointer<SomeDialog> dlg = new SomeDialog(this);
    if (dlg.data()->exec() == QDialog::Accepted ) {
        const QString str = dlg.data()->someUserInput();
        //do something with with str
    } else if(!dlg) {
        //....
    }
    if (!dlg) {
        delete dlg.data();
    }
}

感兴趣的可以去看看原文。比较起来 QDialog::open() 应该更值得考虑。

QCoreApplication::sendPostedEvents()

当程序做繁重的操作时,而又不愿意开启一个新线程时,我们都会选择调用

 QCoreApplication::sendPostedEvents ()

来使得程序保持相应。这和前面提到的哪些exec()开启局部事件循环的效果其实是完全一样的。

无论是这个,还是QEventLoop::exec()最终都是调用:

QAbstractEventDispatcher::processEvents()

来进行事件派发。前面的问题也都是由它派发的事件引起的。

参考


分享到:
评论

相关推荐

    基于Qt事件循环的异步任务管理.zip

    在本文中,我们将深入探讨如何在Qt框架下利用事件循环实现异步任务管理。Qt是一个跨平台的应用程序开发框架,广泛应用于C++编程,尤其在图形用户界面(GUI)应用程序中。"基于Qt事件循环的异步任务管理.zip"文件中的...

    QT事件学习

    QT 的事件循环是异步的,当调用 QApplication::exec() 时,就进入了事件循环,先处理 Qt 事件队列中的事件,再处理系统消息队列中的消息。 事件的派发和处理是通过事件过滤器实现的。事件过滤器可以让一个对象侦听...

    Qt事件机制浅析

    事件循环的主要作用是处理Qt事件队列中的事件以及系统消息队列中的消息。具体流程如下: - 首先处理Qt事件队列中的事件,直到队列为空; - 接着处理系统消息队列中的消息,直到队列为空; - 在处理系统消息的过程中...

    Qt事件处理之鼠标处理事件

    这些事件会被Qt的事件循环捕获,然后根据事件类型分发给相应的对象进行处理。对象通过重写`QObject`的`event`函数或特定的事件处理函数来接收并处理事件。 对于【标题】"Qt事件处理之鼠标处理事件",我们关注的是...

    为Qt添加libev的高性能事件循环qt_eventdispatcher_libev.zip

    qt_eventdispatcher_libev 是基于 libev 的 Qt 事件调度器。 特点 非常快速 ... 支持 Qt4 和 Qt5 ... 不需要 Qt 的私有头文件 ... 通过了 Qt4 和 Qt5 的事件调度,事件循环,定时器和 socket 通知测试

    第9章 Qt事件机制与原理

     实际上a.exec()便是Qt程序进入事件消息循环, 9.1.2 图形界面应用程序的消息处理模型 回调、os的魔抓windows、linux,从用户层到 内核层,如何管理进程、线程、 Os如何处理、底层机制 特点: 基于操作系统...

    QT编程----事件

    事件循环的处理流程是:先处理 QT 事件队列中的 posted 事件,直至为空,然后再处理系统消息队列中的 spontaneous 消息,直至为空。在处理系统消息的时候会产生新的 QT posted 事件,需要对其再次进行处理。 ...

    Qt事件处理,定时器

    1. **事件循环**:每个Qt应用程序都运行在一个事件循环中,该循环不断地检查事件队列并分发事件。当有新事件到达时,事件循环会调用相应的事件处理器。 2. **事件对象**:事件对象继承自`QEvent`,例如`QMouseEvent...

    QT实现的可移动放大缩小的大小嵌套窗体

    在“QT实现的可移动放大缩小的大小嵌套窗体”这个项目中,开发者利用QT库构建了一个独特的窗口系统,允许用户对窗体进行自由移动、缩放,并且窗体之间可以嵌套。以下将详细介绍实现这些功能的关键知识点。 1. **...

    Qt中的事件机制

    3. **发送事件**:这类事件也是由Qt或应用程序产生的,但它们不是通过事件循环处理,而是直接发送给目标对象。 #### 二、事件循环机制 当程序执行到`QApplication::exec()`时,Qt进入事件循环。事件循环是Qt应用...

    为Qt添加libevent的高性能事件循环qt_eventdispatcher_libevent.zip

    qt_eventdispatcher_libevent 是基于 Libevent 的 Qt 事件调度器 特点 非常快速 ... 支持Qt4和Qt5 ... 不需要Qt的私有头文件 ... 通过Qt4 和 Qt5 的事件调度,事件循环,定时器和socket通知测试

    Qt自定义事件,Qt线程应用

    当一个事件发生时,Qt会将该事件放入一个事件队列,然后由事件循环(event loop)分发到相应的对象进行处理。要创建自定义事件,你需要: 1. 定义一个新的C++类,继承自`QEvent`。 2. 在这个类中,你可以添加特定的...

    一个qt图片自动循环播放的小程序

    在本项目中,“一个qt图片自动循环播放的小程序”是一个利用Qt库创建的简单应用,它能自动播放一系列图片,对于初学者了解Qt的事件处理、窗口和控件的使用具有很好的学习价值。 首先,我们需要理解Qt中的关键概念,...

    QT事件系统

    QT事件系统

    12 QT事件重载 QEvent.zip

    当一个事件发生时,Qt会将这个事件放入事件队列,然后由事件循环取出并分发给相应的部件。默认情况下,部件会调用event()函数来处理事件,这个函数会根据事件类型决定是否调用对应的事件处理函数,如mousePressEvent...

    QT的事件处理机制

    3. **事件循环(Event Loop)**:QT的应用程序运行在一个事件循环中,这个循环不断地监听事件的发生,并将它们发送到合适的处理函数进行处理。 #### 三、事件处理流程 QT中的事件处理主要依赖于以下几个步骤: 1. *...

    Qt for android触摸手势事件QGestureEvent

    在本文中,我们将深入探讨如何在Qt for Android环境中利用QGestureEvent处理触摸手势事件。Qt是一个跨平台的应用程序开发框架,支持多种操作系统,包括Android。它提供了丰富的功能,使开发者能够在移动设备上创建...

    Qt字幕从右到左循环滚动

    总的来说,实现"Qt字幕从右到左循环滚动"涉及到了Qt的图形部件操作、动画系统、事件处理和自定义绘图等多个方面的知识。通过熟练运用这些技术,开发者可以创造出各种动态、交互式的用户界面元素,提升软件的视觉效果...

    QT linux下获取键盘和鼠标事件

    在QT框架下开发Linux应用程序时,获取键盘和鼠标事件是常见的需求,这有助于实现用户交互和界面控制。本文将深入探讨如何在Linux环境下利用QT库来捕获和处理这些事件。 首先,QT是一个跨平台的应用程序开发框架,...

    qt事件过滤器实现屏幕无操作超时通知

    在嵌入式qt项目中,有时并不...在qt项目中,可以通过重写事件过滤器来实现屏幕操作的检测,加上定时器的时间控制,可以实现指定时间内没有屏幕操作,给应用程序发送一个信号;通过这个方式,也可以用于屏保应用的实现

Global site tag (gtag.js) - Google Analytics