在Qt中,当使用QApplication :: processEvents()时,发现所有窗口都关闭了?

时间:2013-10-13 08:41:39

标签: c++ qt

有一个QApplication::lastWindowClosed()信号。 Qt docs说:

This signal is emitted from QApplication::exec()
when the last visible primary window [...] is closed.

但是,我在循环中使用了QApplication::processEvents()而不是QApplication::exec()。看到这个最小的例子。 (另存为qapp.h,必须以.h结尾,然后运行qmake -project && qmake && make

#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QMainWindow>

int exitprogram = 0;

class MyMainWindow : public QMainWindow
{
    Q_OBJECT
public slots:
    void request_exit() {
        qDebug() << "exit requested";
        exitprogram = 1;
    }
};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    MyMainWindow w;
    QObject::connect(&app, SIGNAL(lastWindowClosed()),
        &w, SLOT(request_exit()));
    w.show();
    while(!exitprogram)
    {
        app.processEvents(QEventLoop::AllEvents, 20);
    }
}

如果关闭最后一个这样的窗口,是否还有一个很好的方法可以找到或甚至得到一个信号?

2 个答案:

答案 0 :(得分:4)

使用processEvents代替exec的原因是错误的。这两个等价。例如,exec()将处理延迟删除事件,而processEvents则不会。正如您刚刚发现的那样,lastWindowClosed信号也不会发出。这应该告诉你,你做错了。

每次事件循环进行另一次迭代时,做某事的惯用Qt方法是使用零超时计时器。这些是不使用操作系统资源的虚拟计时器,它们是内部Qt结构。

以下示例说明了以下内容:

  1. QObject

  2. 中使用零超时计时器
  3. 使用State Machine Framework来管理应用程序的状态。我们有三种状态:

    • sWindows是仍然显示应用程序窗口的状态。应用程序设置 not 以在最后关闭的窗口上退出。

    • sSetup是最后一个窗口关闭时达到的状态。在这种状态下,我们要求Object发送其通知信号,其执行零超时计时器的次数。这将在message标签(C ++ 11代码)或count标签(遗留代码)中设置正确的计数。状态机自动转换到以下状态。

    • sMessage是显示消息标签的状态,应用程序设置为在最后一个窗口关闭时退出。

  4. 状态机的使用导致声明性代码:您告诉状态机如何行为,而不实现所有行为。您只需要实现特定于您的应用程序的行为,而不是Qt已经提供的行为。状态机管理的对象可以非常分离,并且声明机器行为的代码是有凝聚力的 - 它可以是一个函数,而不是传播。这被认为是很好的软件设计。

    请注意,零超时计时器非常勤奋:只要事件循环为空,它将强制处理程序代码始终执行 。这将强制执行GUI线程的核心100%的CPU消耗。如果你无事可做,你应该stop()计时器。

    Qt 5 C ++ 11 Code

    // https://github.com/KubaO/stackoverflown/tree/master/questions/close-process-19343325
    #include <QtWidgets>
    
    int main(int argc, char** argv)
    {
        QApplication app{argc, argv};
        QLabel widget{"Close me :)"};
        QLabel message{"Last window was closed"};
    
        int counter = 0;
        auto worker = [&]{
           counter++;
        };
        QTimer workerTimer;
        QObject::connect(&workerTimer, &QTimer::timeout, worker);
        workerTimer.start(0);
    
        QStateMachine machine;
        QState sWindows{&machine};
        QState sSetup  {&machine};
        QState sMessage{&machine};
    
        sWindows.assignProperty(qApp, "quitOnLastWindowClosed", false);
        sWindows.addTransition(qApp, &QGuiApplication::lastWindowClosed, &sSetup);
    
        QObject::connect(&sSetup, &QState::entered, [&]{
           workerTimer.stop();
           message.setText(QString("Last window was closed. Count was %1.").arg(counter));
        });
        sSetup.addTransition(&sMessage);
    
        sMessage.assignProperty(&message, "visible", true);
        sMessage.assignProperty(qApp, "quitOnLastWindowClosed", true);
    
        machine.setInitialState(&sWindows);
        machine.start();
        widget.show();
        return app.exec();
    }
    

    Qt 4/5 C ++ 11 Code

    #include <QApplication>
    #include <QLabel>
    #include <QStateMachine>
    #include <QBasicTimer>
    
    class Object : public QObject {
        Q_OBJECT
        QBasicTimer m_timer;
        int m_counter = 0;
    protected:
        void timerEvent(QTimerEvent * ev) {
            if (ev->timerId() == m_timer.timerId())
                m_counter ++;
        }
    public:
        Object(QObject * parent = 0) : QObject{parent} {
            m_timer.start(0, this);
        }
        Q_SLOT void stop() const {
          m_timer.stop();
          emit countedTo(m_counter);
        }
        Q_SIGNAL void countedTo(int) const;
    };
    
    int main(int argc, char** argv)
    {
        QApplication app{argc, argv};
        Object object;
        QLabel widget{"Close me :)"};
        QLabel message{"Last window was closed"};
        QLabel count;
    
        QStateMachine machine;
        QState sWindows{&machine};
        QState sSetup{&machine};
        QState sMessage{&machine};
    
        sWindows.assignProperty(qApp, "quitOnLastWindowClosed", false);
        sWindows.addTransition(qApp, "lastWindowClosed()", &sSetup);
    
        object.connect(&sSetup, SIGNAL(entered()), SLOT(stop()));
        count.connect(&object, SIGNAL(countedTo(int)), SLOT(setNum(int)));
        sSetup.addTransition(&sMessage);
    
        sMessage.assignProperty(&message, "visible", true);
        sMessage.assignProperty(&count, "visible", true);
        sMessage.assignProperty(qApp, "quitOnLastWindowClosed", true);
    
        machine.setInitialState(&sWindows);
        machine.start();
        widget.show();
        return app.exec();
    }
    
    #include "main.moc"
    

答案 1 :(得分:0)

我修改了你的代码,现在它可以工作了。我覆盖了 closeEvent

==> test.cpp <==
#include "windows.hpp"

int exitprogram = 0;

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    MyMainWindow w;
    w.show();
    while(!exitprogram)
    {
        app.processEvents(QEventLoop::AllEvents, 20);
    }
}

==> test.pro <==
TEMPLATE = app
TARGET = test
QT += widgets
INCLUDEPATH += .
HEADERS += windows.hpp

# Input
SOURCES += test.cpp

==> windows.hpp <==
#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QMainWindow>
#include <QCloseEvent>

extern int exitprogram;

class MyMainWindow : public QMainWindow
{
    Q_OBJECT
public slots:
    void closeEvent(QCloseEvent *event) override {
        exitprogram = 1;
        event->accept();
    }
};