如何检测Windows休眠唤醒后Qt程序恢复运行并处理事件
Qt检测Windows休眠唤醒事件的实现方案
一、通过QWidget::nativeEvent实现事件监听
核心步骤
- 注册正确的电源通知:使用
RegisterPowerSettingNotification注册GUID_SYSTEM_POWER_STATE(对应系统电源状态变化,而非显示器状态),需在窗口构造时获取HWND并完成注册。 - 重写
nativeEvent处理消息:拦截Windows原生的WM_POWERBROADCAST消息,根据不同参数判断休眠/唤醒事件。
代码示例
#include <QMainWindow> #include <windows.h> #include <powrprof.h> #pragma comment(lib, "powrprof.lib") class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { HWND hwnd = reinterpret_cast<HWND>(winId()); // 注册系统电源状态变化通知 RegisterPowerSettingNotification(hwnd, &GUID_SYSTEM_POWER_STATE, DEVICE_NOTIFY_WINDOW_HANDLE); } protected: bool nativeEvent(const QByteArray &eventType, void *message, long *result) override { MSG* msg = static_cast<MSG*>(message); if (msg->message == WM_POWERBROADCAST) { switch (msg->wParam) { case PBT_APMSUSPEND: // 系统即将进入休眠,执行自定义处理逻辑 break; case PBT_APMRESUMEAUTOMATIC: // 系统自动唤醒(如定时任务触发) break; case PBT_APMRESUMESUSPEND: // 用户手动唤醒系统 break; case PBT_POWERSETTINGCHANGE: // 获取更详细的电源状态信息 POWERBROADCAST_SETTING* setting = reinterpret_cast<POWERBROADCAST_SETTING*>(msg->lParam); if (IsEqualGUID(setting->PowerSetting, GUID_SYSTEM_POWER_STATE)) { DWORD powerState = *reinterpret_cast<DWORD*>(setting->Data); // powerState对应不同电源状态,如0=工作状态,1=休眠等 } break; } } return QMainWindow::nativeEvent(eventType, message, result); } };
注意点
不要使用GUID_SESSION_DISPLAY_STATUS,该GUID仅用于监听显示器的开启/关闭/变暗,与系统休眠唤醒无关。
二、nativeEvent与QCoreApplication::installNativeEventFilter的选择
- 单个窗口需求:优先用
nativeEvent,代码仅绑定目标窗口,耦合度低,实现简单。 - 全局应用需求:如果多个窗口或整个应用都需要监听,使用
installNativeEventFilter更高效,只需注册一次全局过滤器,即可处理所有窗口的原生事件,避免重复代码。 - 两者性能无显著差异,均直接处理Windows原生消息,选择取决于应用架构。
三、使用PowerRegisterSuspendResumeNotification的方案
这是Windows Vista及以上版本提供的专用API,专门用于监听休眠/唤醒事件,无需拦截窗口消息。
代码示例
- 定义回调函数
#include <QDebug> #include <windows.h> #include <powrprof.h> #pragma comment(lib, "powrprof.lib") VOID CALLBACK PowerSuspendResumeCallback(PVOID Context, ULONG Type, PVOID Setting) { switch (Type) { case PBT_APMSUSPEND: qDebug() << "系统进入休眠"; // 若需执行Qt UI操作,需切换到主线程 // QMetaObject::invokeMethod(qApp, [](){ /* UI操作 */ }, Qt::QueuedConnection); break; case PBT_APMRESUMEAUTOMATIC: qDebug() << "系统自动唤醒"; break; case PBT_APMRESUMESUSPEND: qDebug() << "用户唤醒系统"; break; } }
- 注册与注销通知
int main(int argc, char *argv[]) { QApplication a(argc, argv); REGISTER_NOTIFICATION_REQUEST req{}; req.Version = REGISTER_NOTIFICATION_REQUEST_VERSION; req.Type = PowerRegisterSuspendResume; req.SuspendResumeCallback = PowerSuspendResumeCallback; req.Context = nullptr; // 可传递自定义上下文数据 HPOWERNOTIFY hNotify; PowerRegisterSuspendResumeNotification(&req, &hNotify); MainWindow w; w.show(); int ret = a.exec(); // 程序退出前注销通知 PowerUnregisterSuspendResumeNotification(hNotify); return ret; }
回调执行说明
- 回调函数运行在系统线程,而非Qt主线程,因此如果要执行UI操作或Qt相关逻辑,必须通过
QMetaObject::invokeMethod切换到主线程,避免线程安全问题。 - 该API是异步触发的,注册后不会阻塞主线程,系统会在事件发生时主动调用回调。
内容的提问来源于stack exchange,提问作者riverofwind
相关产品推荐
相关产品推荐

