我有一个带有通知区域图标的应用程序,因此主窗口可能会忽略关闭事件。我正在尝试保存应用程序退出时的首选项和历史记录。我还想在应用程序运行时处理注销。尽管该应用程序是跨平台的,但它在GNU/Linux中最方便/最有用,因此Windows退出行为就不再那么重要了。
这是一个最小的可编译示例,用于测试不同桌面环境/窗口管理器的行为:

// main.cpp
# include <QApplication>
# include <QMainWindow>
# include <QCloseEvent>
# include <QTimer>
# include <iostream>

class M : public QMainWindow
{
    Q_OBJECT
public:
    ~M();
public slots:
    void onAboutToQuit();
private:
    void closeEvent(QCloseEvent *);
};

M::~M()
{
    std::cout << "M::~M()" << std::endl;
}

void M::onAboutToQuit()
{
    std::cout << "onAboutToQuit()" << std::endl;
}

void M::closeEvent(QCloseEvent * e)
{
    std::cout << "closeEvent()" << std::endl;
    hide();
    QTimer::singleShot(5000, this, SLOT(show()));
    e->ignore();
}

int main(int argc, char * argv[])
{
    QApplication app(argc, argv);

    M m;
    m.setWindowModality(Qt::NonModal);
    m.connect(& app, SIGNAL(aboutToQuit()),
            SLOT(onAboutToQuit()));
    m.show();

    return app.exec();
}

# include "main.moc"

// CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(closeeventbug)

option(QT5 "Use Qt5" OFF)

if(QT5)
    find_package(Qt5Core REQUIRED)
    find_package(Qt5Widgets REQUIRED)
else()
    find_package(Qt4 REQUIRED)
    include_directories(${QT_INCLUDES})
endif()

include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(CMAKE_AUTOMOC ON)

add_executable(closeeventbug main.cpp)

if(QT5)
    qt5_use_modules(closeeventbug Core Widgets)
else()
    target_link_libraries(closeeventbug ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})
endif()

注销时大多数成熟的桌面环境都会尝试关闭可见窗口。但是由于测试应用程序在关闭时不会退出,因此注销被中断/取消。如果在不可见的窗口中执行注销,则它会正常退出(就像我希望的那样)。功能较少的桌面环境/窗口管理器会忽略仍在运行的应用程序并退出。它们中的大多数甚至都没有警告应用程序有关注销的信息,因此文件中既没有“closeEvent()”,也没有“onAboutToQuit()”,也没有“M::〜M()”,程序将输出到该文件。被重定向。

详细结果(所有非Windows结果均来自Manjaro GNU/Linux):
  • 完善的桌面环境,如果可见窗口拒绝退出,则取消注销,请优雅地完成不可见的应用程序:
    closeEvent()
    onAboutToQuit()
    M::~M()
        { KDE, XFCE, Mate, GNOME, Cinnamon }
    

    所有其他应用程序都不会取消退出,但是其中一些尝试警告应用程序。
  • 我不知道为什么onAboutToQuit()出现在日志中,但是在这种情况下M::〜M()不存在...
    closeEvent()
    onAboutToQuit()
        { Windows7 }
    

  • 3。
       closeEvent()
           { icewm, E17 }
    

    4,
       <nothing in the log>
           { RazorQt, LxDE, LxQt, i3, budgie, fluxbox, awesome, openbox,
             wmii, E16, pekWM, uwm, fvwm, xmonad, spectrwm, windowmaker,
             herbstluftwm, WindowsXP }
    

    对于(GCC 4.9.1或Clang 3.4.2)和(Qt 4.8.6或Qt 5.3.1)的任何组合,其行为是完全相同的。但是,当我在Xubuntu上尝试Qt 4.8和Qt 5.2时,结果有所不同:XFCE中没有使用Qt 5.2进行阻塞-不管主窗口可见性如何,应用程序都能正常完成。但是Qt 4.8中存在阻塞(与Manjaro中相同)。

    我知道有可能正确处理注销(没有阻塞),因为有几个应用程序可以做到这一点。它们都具有通知区域图标,已关闭到通知区域,但不阻止注销。

    基于Qt的
  • :GoldenDict,Transmission-Qt,Kopete;
  • 基于GTK:Audacious,Pidgin。

  • 我检查了基于Qt的源代码,发现在处理closeEvent方面没有什么特别的地方:
    https://github.com/goldendict/goldendict/blob/master/mainwindow.cc
    
    void MainWindow::closeEvent( QCloseEvent * ev )
    {
        if ( cfg.preferences.enableTrayIcon && cfg.preferences.closeToTray )
        {
            ev->ignore();
            hide();
        }
        else
        {
            ev->accept();
            qApp->quit();
        }
    }
    
    
    https://github.com/bfleischer/transmission/blob/master/qt/mainwin.cc
    
    void
    TrMainWindow :: closeEvent( QCloseEvent * event )
    {
        // if they're using a tray icon, close to the tray
        // instead of exiting
        if( !myPrefs.getBool( Prefs :: SHOW_TRAY_ICON ) )
            event->accept( );
        else {
            toggleWindows( false );
            event->ignore( );
        }
    }
    
    void
    TrMainWindow :: toggleWindows( bool doShow )
    {
        if( !doShow )
        {
            hide( );
        }
        else
        {
            if ( !isVisible( ) ) show( );
            if ( isMinimized( ) ) showNormal( );
            //activateWindow( );
            raise( );
            QApplication::setActiveWindow( this );
        }
    }
    
    git clone git://anongit.kde.org/kopete
    
    void KopeteWindow::closeEvent ( QCloseEvent *e )
    {
        // if we are not ok to exit on close and we are not shutting down then just do what needs to be done if a
        // window is closed.
        KopeteApplication *app = static_cast<KopeteApplication *> ( kapp );
        if ( !shouldExitOnClose() && !app->isShuttingDown() && !app->sessionSaving() ) {
            // BEGIN of code borrowed from KMainWindow::closeEvent
            // Save settings if auto-save is enabled, and settings have changed
            if ( settingsDirty() && autoSaveSettings() )
                saveAutoSaveSettings();
    
            if ( queryClose() ) {
                e->accept();
            }
            // END of code borrowed from KMainWindow::closeEvent
            kDebug ( 14000 ) << "just closing because we have a system tray icon";
        }
        else
        {
            kDebug ( 14000 ) << "delegating to KXmlGuiWindow::closeEvent()";
            KXmlGuiWindow::closeEvent ( e );
        }
    }
    

    所以问题是:
  • 如何确保即使可见主窗口,我的应用程序也不会阻止注销?
  • 如何确保在注销时在尽可能多的桌面环境/窗口管理器中调用onAboutToQuit()和〜M()?

  • 我怀疑应该听一些系统信号,但是我不知道到底是哪个...

    最佳答案

    QApplication具有与操作系统 session 操作相关的signal-您可以轻松地处理它。有关更多详细信息,请查看Qt docs Session Management页面

    09-27 21:06
    查看更多