Introduction
I haven’t seen any completely automated programming implementation of power management operations (sleep and hibernate) of Windows, and that’s why I am writing this. In this article, I will explain and demonstrate how can we do sleep (suspend) or hibernate and then resume operations of Windows using different ways in WPF. My main focus will be on without user interaction. The sample code is tested on Vista and Windows 7 Operating Systems.
A waitable timer is basically a kernel object that is signaled at a certain time or at regular intervals (depends on the parameter of the SetWaitableTimer() function). Asynchronous procedure calls (APC) can be associated with a waitable timer object to allow a callback function to be executed when the timer object is signaled. You can create, execute, and cancel a waitable timer by calling the CreateWaitableTimer(), SetWaitableTimer(), and CancelWaitableTimer() functions of Kernel32.dll.
The CreateWaitableTimer() function returns a handle to the kernel object. We may use the OpenWaitableTimer() function to open an existing named waitable timer object. SetWaitableTimer() will set the timer. In the SetWaitableTimer() function’s fourth parameter, we can associate an asynchronous procedure call (APC) function which will be called as a completion routine. We can also pass some arguments to the completion routine using the fifth parameter of this function. The completion routine will always be executed by the same thread which called the SetWaitableTimer() function. So that thread must put itself in an alertable state. This can be accomplished using many ways. I am using SleepEx() of Kernel32.dll.
For the notification of our system’s power mode change, we can use three different ways. One is to use the PowerModeChanged event of the SystemEvents class which is in the Microsoft.Win32 namespace in System.dll. Second one is by using the window message, and the third one is by adding custom events in our application.
The PowerModeChanged event (and our custom event) basically depends on the WM_POWERBROADCAST window message.
PowerModeChanged event
The PowerModeChanged event uses the PowerModes enumeration which defines the identifiers for power mode events reported by the Operating System. First, we need to register this event in our application to receive notifications from the Operating System.
Remember it’s a static event, so we must detach our event handler when our application is disposed; otherwise, it will result in memory lakes.
Using a window message (WM_POWERBROADCAST)
Windows uses the WM_POWERBROADCAST message to inform applications of changes related to power. An application must have to be registered to receive notification messages from Windows. We can find out information about an event while examining the wParam parameter of the window message. A complete list of events is available in the Power Management documentation. To use Win32 window messages in our C# WPF application, we will use the HwndSourceHook delegate which is in the System.Windows.Interop namespace and the assembly is in PresentationCore.dll. For interoperation between Windows Presentation Foundation and Win32, we will use the WindowInteropHelper class.
When the system wakes up because of the result of a timer signal, a window triggers the message PBT_APMRESUMEAUTOMATIC to the application and will set an unattended idle timer for two minutes (based on your settings). This timer gives the applications enough time to call the SetThreadExecutionState() function to indicate the Operating System that you are back with full functionality. If we do not do anything in this unattended idle time interval, then the system will return to the sleep state assuming that the system is no more required.
To synthesize a keystroke, we will use the keybd_event() function to generate a keyboard message when the system is resumed because of the result of a timer signal. In this way, we will definitely get the PBT_APMRESUMESUSPEND window message when we receive the PBT_APMRESUMEAUTOMATIC message.
Using Custom Events for Power Management in WPF
On the basis of your requirements, you can also add custom events in your application. To register a new routed event with the Windows Presentation Foundation (WPF) event system, use RegisterRoutedEvent method of EventManager class.
Using the code
Here is the complete code for all of the three methods mentioned above. In the sample application, it will ask to enter the number of seconds to resume after Sleep or Hibernate. Below is the output of the sample application:

You can get this complete WPF sample application in the attachment. I did not test this on XP, but I did on Vista and Windows 7:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Interop;
namespace SleepHibernateExercise
{
public partial class Window1 : Window
{
private static long SECONDS = 10000000;
const int WM_POWERBROADCAST = 0x0218;
const int PBT_APMPOWERSTATUSCHANGE = 0x000A;
const int PBT_APMSUSPEND = 0x0004;
const int PBT_APMRESUMESUSPEND = 0x0007;
const int PBT_POWERSETTINGCHANGE = 0x8013;
const int PBT_APMRESUMEAUTOMATIC = 0x0012;
const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
const int VK_NONAME = 0x00FC;
private const byte KEYEVENTF_SILENT = 0x0004;
[Flags]
public enum EXECUTION_STATE : uint
{
ES_CONTINUOUS = 0x80000000,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_SYSTEM_REQUIRED = 0x00000001,
ES_AWAYMODE_REQUIRED = 0x00000040
}
public delegate void TimerCompleteDelegate();
public event TimerCompleteDelegate OnTimerCompleted;
private IntPtr handle;
private uint INFINITE = 0xFFFFFFFF;
private DateTime StartTime;
private DateTime EndTime;
[DllImport("kernel32.dll")]
static extern IntPtr CreateWaitableTimer(IntPtr lpTimerAttributes,
bool bManualReset, string lpTimerName);
[DllImport("kernel32.dll")]
static extern bool SetWaitableTimer(IntPtr hTimer, [In] ref long ft,
int lPeriod, TimerCompleteDelegate pfnCompletionRoutine,
IntPtr pArgToCompletionRoutine, bool fResume);
[DllImport("kernel32.dll")]
static extern bool CancelWaitableTimer(IntPtr hTimer);
[DllImport("kernel32.dll")]
static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
[DllImport("kernel32.dll", SetLastError = true,
CallingConvention = CallingConvention.Winapi,
CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("Kernel32.DLL",
CharSet = CharSet.Auto, SetLastError = true)]
protected static extern EXECUTION_STATE
SetThreadExecutionState(EXECUTION_STATE state);
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan,
uint dwFlags, int dwExtraInfo);
public Window1()
{
InitializeComponent();
}
~Window1()
{
Dispose();
}
private IntPtr MessageProc(IntPtr hwnd, int msg, IntPtr wParam,
IntPtr lParam, ref bool handled)
{
if (msg == WM_POWERBROADCAST)
{
if (wParam.ToInt32() == PBT_APMSUSPEND)
{
StartTime = DateTime.Now;
listBox1.Items.Add("Suspend: " +
StartTime.ToLongTimeString());
RaiseSystemSuspendingEvent(); }
if (wParam.ToInt32() == PBT_APMRESUMESUSPEND)
{
listBox1.Items.Add("Resume: " + EndTime.ToLongTimeString());
try
{
CancelWaitableTimer(handle);
}
catch (Exception ex)
{
listBox1.Items.Add("Exception while calling " +
"CancelWaitableTimer(): " +
ex.Message);
}
RaiseSystemResumingEvent();
}
if (wParam.ToInt32() == PBT_APMRESUMEAUTOMATIC)
{
EndTime = DateTime.Now;
listBox1.Items.Add("Auto Resume: " +
DateTime.Now.ToLongTimeString());
SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED |
EXECUTION_STATE.ES_CONTINUOUS);
SetThreadExecutionState(EXECUTION_STATE.ES_DISPLAY_REQUIRED);
if (StartTime.CompareTo(EndTime) != 0)
{
TimeSpan span = EndTime.Subtract(StartTime);
listBox1.Items.Add("Time Difference (milli seconds): " +
span.Milliseconds);
listBox1.Items.Add("Time Difference (seconds): " +
span.Seconds);
listBox1.Items.Add("Time Difference (minutes): " +
span.Minutes);
listBox1.Items.Add("Time Difference (hours): " +
span.Hours);
listBox1.Items.Add("Time Difference (days): " +
span.Days);
listBox1.Items.Add("");
listBox1.Items.Add("Total time taken (between " +
"Sleep/Hibernate and Resume): " +
span.ToString());
}
keybd_event(VK_NONAME, 0, KEYEVENTF_SILENT, 0);
}
}
return IntPtr.Zero;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
SystemEvents.PowerModeChanged +=
new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
WindowInteropHelper helper = new WindowInteropHelper(this);
HwndSource source = HwndSource.FromHwnd(helper.Handle);
source.AddHook(new HwndSourceHook(this.MessageProc));
this.SystemSuspending += new RoutedEventHandler(Window1_SystemSuspending);
this.SystemResuming += new RoutedEventHandler(Window1_SystemResuming);
}
private void Dispose()
{
SystemEvents.PowerModeChanged -=
new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
GC.SuppressFinalize(this);
}
private void SystemEvents_PowerModeChanged(object sender,
PowerModeChangedEventArgs e)
{
if (e.Mode == PowerModes.Suspend)
{
listBox1.Items.Add("Got Suspend message (PowerModeChanged " +
"event): " + DateTime.Now.ToLongTimeString());
listBox1.Items.Add("");
}
if (e.Mode == PowerModes.Resume)
{
listBox1.Items.Add("Got Resume message " +
"(PowerModeChanged event): " +
DateTime.Now.ToLongTimeString());
listBox1.Items.Add("");
}
}
private void SleepButton_Click(object sender, RoutedEventArgs e)
{
if ((SecondsTextBox.Text != "") &&
(Convert.ToInt32(SecondsTextBox.Text) > 0))
{
SetTimer(Convert.ToInt32(SecondsTextBox.Text), PowerState.Suspend);
}
}
private void HibernateButton_Click(object sender, RoutedEventArgs e)
{
if ((SecondsTextBox.Text != "") &&
(Convert.ToInt32(SecondsTextBox.Text) > 0))
{
SetTimer(Convert.ToInt32(SecondsTextBox.Text),
PowerState.Hibernate);
}
}
private void AbortButton_Click(object sender, RoutedEventArgs e)
{
TimerCancel();
}
public void SetTimer(long Interval,PowerState ps)
{
TimerCompleteDelegate TimerComplete =
new TimerCompleteDelegate(TimerCompleted);
listBox1.Items.Add("System is going to " + ps +
" with set the timer to be back after " +
Interval + " seconds");
Interval = -Interval * SECONDS;
handle = CreateWaitableTimer(IntPtr.Zero, true,
"WaitableTimer");
if (Marshal.GetLastWin32Error() != 0)
{
listBox1.Items.Add("Error: " +
Marshal.GetLastWin32Error().ToString());
}
SetWaitableTimer(handle, ref Interval, 0, TimerComplete,
IntPtr.Zero, true);
if (Marshal.GetLastWin32Error() != 0)
{
listBox1.Items.Add("Error: " +
Marshal.GetLastWin32Error().ToString());
}
listBox1.Items.Add("Timer set: " +
DateTime.Now.ToLongTimeString());
Thread tSystemState = new Thread(ChangeSystemState);
tSystemState.Start(ps);
Thread t_Wait = new Thread(new ThreadStart(WaitTimer));
t_Wait.Start();
}
private void ChangeSystemState(Object obj)
{
PowerState pp = (PowerState)obj;
if (System.Windows.Forms.Application.SetSuspendState(pp,
true, false) == false)
{
}
}
private void WaitTimer()
{
SleepEx(INFINITE, true);
CloseHandle(handle);
if (OnTimerCompleted != null)
{
OnTimerCompleted();
}
}
private void TimerCompleted()
{
System.Windows.Forms.Application.Run();
listBox1.Items.Add("Timer is complete: " +
DateTime.Now.ToLongTimeString());
}
public void TimerCancel()
{
listBox1.Items.Add("Cancel the Timer");
CancelWaitableTimer(handle);
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
GC.Collect();
System.Windows.Application.Current.Shutdown();
Environment.Exit(0);
}
void Window1_SystemSuspending(object sender, RoutedEventArgs e)
{
listBox1.Items.Add("System is entering suspend mode" +
" (Custom Event): " +
DateTime.Now.ToLongTimeString());
}
void Window1_SystemResuming(object sender, RoutedEventArgs e)
{
listBox1.Items.Add("System is resuming from " +
"a suspend mode (Custom Event): " +
DateTime.Now.ToLongTimeString());
}
public static readonly RoutedEvent SystemSuspendingEvent =
EventManager.RegisterRoutedEvent(
"SystemSuspendingEvent",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(Window1));
public event RoutedEventHandler SystemSuspending
{
add
{
AddHandler(SystemSuspendingEvent, value);
}
remove
{
RemoveHandler(SystemSuspendingEvent, value);
}
}
private void RaiseSystemSuspendingEvent()
{
RoutedEventArgs newEventArgs =
new RoutedEventArgs(Window1.SystemSuspendingEvent, this);
RaiseEvent(newEventArgs);
}
static readonly RoutedEvent SystemResumingEvent =
EventManager.RegisterRoutedEvent(
"SystemResumingEvent",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(Window1));
public event RoutedEventHandler SystemResuming
{
add
{
AddHandler(SystemResumingEvent, value);
}
remove
{
RemoveHandler(SystemResumingEvent, value);
}
}
void RaiseSystemResumingEvent()
{
RoutedEventArgs newEventArgs =
new RoutedEventArgs(Window1.SystemResumingEvent, this);
RaiseEvent(newEventArgs);
}
}
}