【发布时间】:2014-08-03 19:54:22
【问题描述】:
我有一个带有通知区域图标的应用程序,因此主窗口可能会忽略关闭事件。我正在尝试在应用程序退出时保存首选项和历史记录。我还想在应用程序运行时处理注销。虽然该应用程序是跨平台的,但它在 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 OR Clang 3.4.2) AND (Qt 4.8.6 OR 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()?
我怀疑应该听一些系统信号,但我不知道具体是哪个...
【问题讨论】: