李sir_Blog

博客园 首页 联系 订阅 管理

下载本文中所用的代码: PowerAware2007_07.exe (360 KB)

 

http://msdn.microsoft.com/zh-cn/magazine/cc163386.aspx


浏览在线代码

随着移动计算机的日益流行,电池的寿命变得越来越重要了。硬件制造商已经意识到了这一点,并在设计移动设备时考虑到了电池损耗问题。但对于您的软件来说,也同样如此吗?如果不是这样,可能您就应考虑使用能够感知电源的编码软件了。Windows® XP 和 Windows Vista™ 都提供了应用程序实现此项功能所需的系统信息。在本文中,我会提供一个起点,供开发人员了解其 Windows Presentation Foundation 应用程序中的感知电源。
首先,您必须了解 Windows Presentation Foundation 本身并不会自发地对可能的感知电源应用程序提供电源通知。您可以从我的示例中看到,此功能取决于操作系统 — 可以是 Windows XP,也可以是 Windows Vista(运行 Windows Presentation Foundation 的操作系统)。
在向您展示如何将此信息加入 Windows Presentation Foundation 应用程序之前,我注意到 Windows Vista 上的电源通知系统要比 Windows XP 上的电源通知系统先进。Windows Vista 不仅包含 Windows XP 电源通知系统的所有功能,另外还有一些功能,这就带来了一定的挑战。什么是通知系统?它们有何差异?如何管理这些差异以设计出成功的感知电源应用程序?根据 Windows Presentation Foundation 应用程序要既能在 Windows XP 上运行,又能在 Windows Vista 上运行这一实际情况,上述问题尤为重要。

 

现有任务
对于正在寻求 Windows XP 和 Windows Vista 电源通知系统的示例代码和动手体验的人员,有两个专门为其设计的很棒的动手实验室。这两个实验室位于(适用于 Windows XP)和(适用于 Windows Vista)。这些实验室包含可在托管代码中使用的电源通知系统;但这些实验室并未提供将电源通知系统特定用于 Windows Presentation Foundation 的方式。同时,这些实验室提供了一个绝佳的起点,并将通知系统作为一个整体包括在内,其中的处理比我在此处提供的要详细得多。建议您与本文结合使用这两个实验室。
另一个要点是每个应用程序对电源损耗的关注各不相同。在系统即将进入暂停状态时,并非每个应用程序都会执行 3D 动画或向磁盘写入数据。所以,单个示例或动手实验室事实上无法满足所有应用程序的全部电源需求。因此,我鼓励开发人员根据其应用程序需要的具体环境,认真查看每个通知系统的详细信息以及本文中所采取的措施。

 

代码概述
在本文中,我将尝试调节 Windows XP 和 Windows Vista 电源通知系统之间的差异,以便在 Windows Presentation Foundation 应用程序中使用。大多数的任务都在如下自定义类的内部执行:PowerAwareWindow。这个类由 Window 类(存放许多应用程序的元素)派生而来,它使用自定义的依赖关系属性和事件以典型的 Windows Presentation Foundation 样式公开许多与电源相关的系统信息和通知。此自定义类的定义可从 PowerAwareWindow.cs 文件中找到(本文中的所有代码都可从 MSDN® 杂志网站下载)。
使操作系统能够发出电源通知的函数、变量及关联的结构位于本机代码中。为使在 PowerAwareWindow 类中执行的任务更具可读性,这些本机元素都位于单独的代码文件 NativeMethods.cs 中。
使用 PowerAwareWindow 的示例应用程序存放于 Window1.xaml 和 Window1.xaml.cs 文件中。此应用程序驻留在 PowerAwareWindow 中,而不是驻留在标准的 Window 中;它挂接到自定义事件,并利用在 PowerAwareWindow 中声明的自定义依赖关系属性接收和响应系统电源状态的更改。
请注意,下载的示例代码与此处显示的代码会略有不同。该示例的许多本机元素都位于 NativeMethods.cs 文件中。因此,在示例代码中,引用的本机元素都以 NativeMethods 作为前缀。例如,可下载代码会使用以下形式引用操作系统版本:
NativeMethods.VISTA_OS_VERSION
但在本文中,为清晰起见,我只使用以下形式进行引用:
VISTA_OS_VERSION
另外,为使其更具可读性,格式上稍有不同,注释也有些不同,并且下载的代码包含一些 Debug.Assert 调用。这些调用作为额外的预防措施包括在内,因为有些代码部分假设应用程序运行在 Windows Vista 上,而其他部分假设应用程序运行在 Windows XP 上。

 

系统电源更改通知
第一步是获得与电源相关的系统更改通知。如前所述,本文不提供完整的 Windows XP 和 Windows Vista 通知系统概述。我仅介绍此功能中特定于在 Windows Presentation Foundation 中使用的部分。(要获取通知系统的完整概述,应查看“其他资源”侧栏中列出的“移动 PC 开发指南”。)
我将介绍两种获取系统电源更改通知的方法:使用 System.dll 的 Microsoft.Win32 命名空间中 SystemEvents 类上的 PowerModeChanged 事件,以及通过捕获 WM_POWERBROADCAST 窗口消息。
PowerModeChanged 事件实际上是在 WM_POWERBROADCAST 消息的基础上构建的,但此构建是在将与电源相关的新功能添加到 Windows Vista 之前完成的。这样,在 Windows Vista 上,后一种方法更加有效。例如,在仅使用 PowerModeChanged 事件时,应用程序对 Windows Vista 用户定义的电源计划(稍后讨论)一点也不了解。

 

PowerModeChanged 事件和处理程序
PowerModeChanged 事件会将 PowerModes 枚举传递到注册的事件处理程序。此枚举具有三个值:Resume、StatusChange 和 Suspend。使用此事件,会在以下时刻通知应用程序:
  • 操作系统将从暂停状态继续 (Resume)
  • 电源在交流电源与电池之间进行了转换,或者对系统电源供应的状态进行了更改 (StatusChange)
  • 操作系统将暂停 (Suspend)
在 PowerAwareWindow 中,此事件通常挂接在 Window.Loaded 事件处理程序中,而取消在 Window.Closed 事件处理程序中的挂接(请参见图 1)。当然,上述操作要在 Window.Loaded 和 Window.Closed 事件挂接之后执行。此事件涵盖了许多可能会对应用程序有用的与电源相关的信息,因此,对您的感知电源应用程序来说,此事件可能是最佳选项。如果某个应用程序仅需要此信息,则使用此方法接收电源事件比我马上要介绍的窗口消息捕获方法更直接。

protected override void OnInitialized(EventArgs e){    // Hook up the Window.Loaded and Window.Closed events    this.Loaded += new RoutedEventHandler(PowerAwareWindow_Loaded);    this.Closed += new EventHandler(PowerAwareWindow_Closed);     base.OnInitialized(e);}void PowerAwareWindow_Loaded(object sender, RoutedEventArgs e){                Microsoft.Win32.SystemEvents.PowerModeChanged +=         new PowerModeChangedEventHandler(            XP_SystemEvents_PowerModeChanged);}void PowerAwareWindow_Closed (object sender, RoutedEventArgs e){                Microsoft.Win32.SystemEvents.PowerModeChanged -=         new PowerModeChangedEventHandler(            XP_SystemEvents_PowerModeChanged);}

在代码下载中实现了 XP_SystemEvents_PowerModeChanged 函数,您可以在其中进行查看。在示例中没有利用此函数,因为用于挂接事件的代码已被注释掉,仅作演示之用。

 

捕获 WM_POWERBROADCAST 窗口消息
操作系统将发出 WM_POWERBROADCAST 窗口消息,通知侦听应用程序对与电源相关的状态或设置做出的更改。但是为了接收其中的某些通知,应用程序必须首先对其进行注册。为了保证完整性,本文附带的代码示例使用窗口消息来实现电源通知(而不是 PowerModeChanged 事件)。
使用 WM_POWERBROADCAST,应用程序可以接收所有的系统电源更改通知。为了在 Windows Presentation Foundation 应用程序中接收窗口消息,必须首先按以下方式声明事件处理程序,才能接收窗口消息:
void PowerAwareWindow_Loaded(object sender, RoutedEventArgs e){                WindowInteropHelper helper = new WindowInteropHelper(this);    HwndSource source = HwndSource.FromHwnd(helper.Handle);    source.AddHook(new HwndSourceHook(this.MessageProc));    ...}
上述代码将 MessageProc 函数声明为窗口消息事件处理程序。该函数将检查 WM_POWERBROADCAST 消息(请参见图 2),并据此执行操作。

const int WM_POWERBROADCAST = 0x0218;IntPtr MessageProc(    IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled){    switch (msg)    {        case WM_POWERBROADCAST:            // Handle the message here...            break;       // Handle other window messages (if the application needs to) here    }}

有关事件的信息包含在窗口消息的 wParam 中。wParam 可采用不同的形式来表示与电源相关的不同系统事件。例如,下面的值表示电源或电量剩余百分比已更改:
const int PBT_APMPOWERSTATUSCHANGE = 0x000A;
以下代码表明系统将进入或正在离开暂停状态:
const int PBT_APMSUSPEND = 0x0004;const int PBT_APMRESUMESUSPEND = 0x0007;
部分值不适用于所有平台。例如,下面显示的值在启动 Windows Vista 时可用,表明以下各项之一已更改:电源、电源计划、电量剩余百分比或监视器状态。
const int PBT_POWERSETTINGCHANGE = 0x8013;
此类事件的完整列表可从电源管理文档中找到。比较而言,PBT_APMPOWERSTATUSCHANGE、PBT_APMSUSPEND 和 PBT_APMRESUMESUSPEND wParams 分别类似于 PowerModeChange 事件中的 StatusChange、Suspend 和 Resume 电源模式。
重点要注意 PBT_APMPOWERSTATUSCHANGE 和 PBT_ POWERSETTINGCHANGE 各自的 wParam 值之间的差异。后者可理解为 PBT_APMPOWERSTATUSCHANGE 的仅适用于 Windows Vista 的超集。它表示由 PBT_APMPOWERSTATUSCHANGE 指示的与电源相关的所有更改,以及其他仅特定于 Windows Vista 的信息(如电源计划和监视器状态)。这两个 wParam 都存在并随 Windows Vista 上的 WM_POWERBROADCAST 窗口消息一起传递,但当在 Windows Vista 上运行此示例时,同时处理二者会导致冗余。
但在 Windows Vista 应用程序注册电源设置通知前,不会将 PBT_POWERSETTINGCHANGE wParam 发送到此应用程序(无需注册即可接收 PBT_APMPOWERSTATUSCHANGE)。对于接收特定于 Windows Vista 的电源通知而言,此步骤至关重要。

 

在 Windows Vista 上注册通知
要在 Windows Vista 上注册电源设置通知,首先要使 RegisterPowerSettingNotification 函数在应用程序中可用。您可以通过定义以下 P/Invoke 函数执行上述操作:
[DllImport(@”User32.dll”, SetLastError=true,    CallingConvention=CallingConvention.StdCall)]static extern IntPtr RegisterPowerSettingNotification(    IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);
此函数返回一个句柄,在关闭应用程序时此句柄用于注销事件。
传递到此函数的 Flag 用于向操作系统指明应用程序接收通知的方式。在此示例中,我对此进行以下声明:
const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
这用于向注册函数指明将通知发送至使用 WM_POWERBROADCAST 窗口消息(带有 PBT_POWERSETTINGCHANGE 的 wParam)的应用程序。Flag 的另一个选项与操作系统服务有关,在先前提到的 RegisterPowerSettingNotification 文档中有介绍。
传递到注册函数的 PowerSettingGuid 值用于向操作系统指明对应用程序有用的通知。图 3 显示了您最有可能用到的四个 PowerSettingGuid 值。

// notify application when percentage of battery remaining changesstatic Guid GUID_BATTERY_PERCENTAGE_REMAINING =     new Guid(“A7AD8041-B45A-4CAE-87A3-EECBB468A9E1”);// notify application when the state of the monitor changesstatic Guid GUID_MONITOR_POWER_ON =     new Guid(“02731015-4510-4526-99E6-E5A17EBD1AEA”);// notify application when the system power source changesstatic Guid GUID_ACDC_POWER_SOURCE =     new Guid(“5D3E9A59-E9D5-4B00-A6BD-FF34FF516548”);// notify application when the user-defined power plan changesstatic Guid GUID_POWERSCHEME_PERSONALITY =     new Guid(“245D8541-3943-4422-B025-13A784F679B7”);

其他的通知 GUID 均可用,并可用于获取一些事件(如系统进入或退出离开模式、系统在即将到来的某个时间将进入空闲状态以及当前时间适合执行后台任务或空闲任务)的通知。
在此示例中,我已将所有的 Windows Vista 电源注册组合到了一个函数中(请参见图 4)。同样地,我也将所有的电源注销组合到了一个函数中。注册发生在 PowerAwareWindow 加载时(PowerAwareWindow_Loaded 函数处理 Window.Loaded 事件时),注销发生在 PowerAwareWindow 关闭时(PowerAwareWindow_Closed 处理 Window.Closed 时)。

IntPtr _hBattCapacity;IntPtr _hMonitorOn;IntPtr _hPowerScheme;IntPtr _hPowerSrc;void RegisterForVistaPowerNotifications(IntPtr hWnd){    _hPowerSrc = RegisterPowerSettingNotification(        hWnd, ref GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);    _hBattCapacity = RegisterPowerSettingNotification(        hWnd, ref GUID_BATTERY_PERCENTAGE_REMAINING,         DEVICE_NOTIFY_WINDOW_HANDLE);    _hMonitorOn = RegisterPowerSettingNotification(        hWnd, ref GUID_MONITOR_POWER_ON, DEVICE_NOTIFY_WINDOW_HANDLE);    _hPowerScheme = RegisterPowerSettingNotification(        hWnd, ref GUID_POWERSCHEME_PERSONALITY,         DEVICE_NOTIFY_WINDOW_HANDLE);}void UnregisterForVistaPowerNotifications(){    UnregisterPowerSettingNotification(_hBattCapacity);    UnregisterPowerSettingNotification(_hMonitorOn);    UnregisterPowerSettingNotification(_hPowerScheme);    UnregisterPowerSettingNotification(_hPowerSrc);}

注册会引入我要在此示例中解决的主要问题之一,即在一个 Windows Presentation Foundation 应用程序中创建一致的感知电源方法,解决 Windows XP 和 Windows Vista 上关于电源通知的差异。从 User32.dll 导出的 RegisterPowerSettingNotification 和 UnregisterPowerSettingNotification 函数不适用于 Windows Vista 之前的操作系统,因此,尝试使用这两个函数的所有代码都要首先确保函数可用才能使用它们。

 

检测操作系统
对于您的应用程序来说,检测操作系统并根据检测结果执行相应操作是很重要的。为此,我的示例中使用了标记为只读的私有变量来存储此信息:
readonly int _osVersion = Environment.OSVersion.Version.Major;
Windows Vista 和 Windows XP 的操作系统主要版本号是:
const int VISTA_OS_VERSION = 6;const int XP_OS_VERSION = 5;
此时,PowerAwareWindow_Loaded 和 PowerAwareWindow_Closed 函数应与图 5 中所示类似。此时,应用程序通过 MessageProc 函数中的窗口消息接收电源通知。收到电源通知后,应用程序需要进行适当的响应。

void PowerAwareWindow_Loaded(object sender, RoutedEventArgs e){    // Optional: If the PowerModeChanged event contains the desired power-    // notification functionality for an application, no OS detection is    // necessary as PowerModeChanged will work with both    SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(        XP_SystemEvents_PowerModeChanged);    // Use the helper to hook up our Windows Presentation Foundation window     // to receive window messages.    WindowInteropHelper helper = new WindowInteropHelper(this);    HwndSource source = HwndSource.FromHwnd(helper.Handle);    source.AddHook(new HwndSourceHook(this.MessageProc));    if (_osVersion >= VISTA_OS_VERSION)    {        RegisterForVistaPowerNotifications(source.Handle);        // There’s no need to initialize the dependency properties on        // Windows Vista because this application will get notified of the         // power status when it registers for the events.    }    else    {        // Application power initialization should be done here (otherwise         // the application is left waiting for a WM_POWERBROADCAST window         // message that might not come right away).    }}void PowerAwareWindow_Closed(object sender, EventArgs e){    // Optional: Unregister for PowerModeChanged if registration for it    // was done in PowerAwareWindow_Loaded    SystemEvents.PowerModeChanged -= new PowerModeChangedEventHandler(        XP_SystemEvents_PowerModeChanged);        if (_osVersion >= VISTA_OS_VERSION)    {        UnregisterForVistaPowerNotifications();    }    else    {        // Do Windows XP-specific application cleanup here    }}

自定义电源依赖关系属性
大多数情形下,除本示例中声明的依赖关系属性外,感知电源应用程序还有必要声明和使用自定义依赖关系属性。请注意,自定义依赖关系属性是一个高级主题。如果您在阅读此部分之前需要了解更多的背景信息,请查看自定义依赖关系属性文档。
这些自定义依赖关系属性可能看起来相当复杂,但却对使用 PowerAwareWindow 的应用程序提供了强大的功能和灵活性。本示例中声明的自定义依赖关系属性有 PowerPlan、MonitorOn、RemainingBattery 和 RunningOnBattery。
PowerPlan 公开了当前用户指定的电源计划。此信息仅适用于 Windows Vista。在 Windows XP 上,此属性总是使用默认值 (Automatic)。相关联的 PowerPlanChanged 事件在 Windows XP 上不会引发,因为该操作系统上没有电源计划的概念。
PowerPlan 枚举的定义如下:
public enum PowerPlan{    Automatic,    HighPerformance,    PowerSaver,};
在 Windows Vista 用户界面中,它们分别称为“已平衡”、“高性能”和“节能程序”。依赖关系属性注册类似于图 6 中所示的代码。

private static readonly DependencyPropertyKey PowerPlanPropertyKey =    DependencyProperty.RegisterReadOnly(        “PowerPlan”, typeof(PowerPlan), typeof(UIElement),        new FrameworkPropertyMetadata(PowerPlan.Automatic,             FrameworkPropertyMetadataOptions.Inherits |             FrameworkPropertyMetadataOptions.Journal |             FrameworkPropertyMetadataOptions.                OverridesInheritanceBehavior,             new PropertyChangedCallback(OnPowerPlanChanged)));public static readonly DependencyProperty PowerPlanProperty =     PowerPlanPropertyKey.DependencyProperty;private static void OnPowerPlanChanged(    DependencyObject d, DependencyPropertyChangedEventArgs e){    PowerAwareWindow myPowerAwareWindow = d as PowerAwareWindow;    if (myPowerAwareWindow != null &&        myPowerAwareWindow.PowerPlanChanged != null)    {        myPowerAwareWindow.PowerPlanChanged(myPowerAwareWindow, e);    }}public event DependencyPropertyChangedEventHandler PowerPlanChanged;public PowerPlan PowerPlan{    get { return (PowerPlan)GetValue(PowerPlanProperty); }    private set    {        SetValue(PowerAwareWindow.PowerPlanPropertyKey, value);    }}

MonitorOn 公开监视器的当前状态 (display) — 打开或关闭。此信息仅适用于 Windows Vista。在 Windows XP 中,此属性总是使用默认值 (True)。相关联的 MonitorOnChanged 事件在 Windows XP 上不会引发,因为该系统上没有监视器状态的概念。
依赖关系属性注册与 PowerPlan 中所述极其类似,不同之处在于属性类型为 Boolean、名为 MonitorOn,对应的事件为 MonitorOnChanged。此属性及其他依赖关系属性的完整实现可在代码下载中找到。
RemainingBattery 公开了电池当前剩余电量的百分比,取值范围从 0% 至 100%。在此情形下,依赖关系属性注册与上述两种依赖关系属性的注册非常类似。
RunningOnBattery 如果系统依赖电池运行,则该值为 True;如果系统依赖交流电源运行,则该值为 False。

 

自定义电源事件
与自定义依赖关系属性类似,感知电源应用程序通常还有必要声明和使用自定义事件。(如果需要有关自定义事件的更多背景知识,请查看路由事件概述。)在我的示例中,自定义事件包括 PreviewSystemResuming、SystemResuming、PreviewSystemSuspending 和 SystemSuspending。
PreviewSystemResuming 系统从暂停状态继续开始时出现。事件的“审核”部分表明事件的路由策略是隧道路由,而不是直接路由或气泡路由。事件注册类似于图 7 中所示的代码。

static readonly RoutedEvent PreviewSystemResumingEvent =    EventManager.RegisterRoutedEvent(“PreviewSystemResumingEvent”,        RoutingStrategy.Tunnel, typeof(RoutedEventHandler),        typeof(PowerAwareWindow));public event RoutedEventHandler PreviewSystemResuming{    add { AddHandler(PreviewSystemResumingEvent, value); }    remove { RemoveHandler(PreviewSystemResumingEvent, value); }}//  This is called when the PowerAware window wants to raise the event void RaisePreviewSystemResumingEvent(){    RaiseEvent(new RoutedEventArgs(        PowerAwareWindow.PreviewSystemResumingEvent, this));}

SystemResuming 系统从暂停状态继续开始时出现。这会用到 PreviewSystemResuming 事件的气泡路由策略版本。事件注册类似于图 8 中所示的代码。

static readonly RoutedEvent SystemResumingEvent =    EventManager.RegisterRoutedEvent(“SystemResumingEvent”,        RoutingStrategy.Bubble, typeof(RoutedEventHandler),        typeof(PowerAwareWindow));public event RoutedEventHandler SystemResuming{    add { AddHandler(SystemResumingEvent, value); }    remove { RemoveHandler(SystemResumingEvent, value); }}//  This is called when the PowerAware window wants to raise the event void RaiseSystemResumingEvent(){    RaiseEvent(new RoutedEventArgs(        PowerAwareWindow.SystemResumingEvent, this);}

PreviewSystemSuspending 系统即将进入暂停状态时出现。事件的“审核”部分表明事件的路由策略是隧道路由,与在 PreviewSystemResuming 中相同。
SystemSuspending 系统即将进入暂停状态时出现。与 SystemResuming 相同,SystemSuspending 使用气泡路由策略。

 

Windows XP 电源结构
自定义依赖关系属性在包含最新的系统信息前,用处并不大。同样,需要相应引发自定义电源事件。对于在 Windows XP 和 Windows Vista 上都能运行的单个应用程序,要使得 Windows Presentation Foundation 感知电源正常运行会涉及到一些复杂问题。现在,就让我们来看看此结构以及上述的部分问题。
我们从重要的 Windows XP 电源结构开始。首先,有一个 SYSTEM_POWER_STATUS(请参见图 9)。您可以看到,本示例使用 ACLineStatus 和 BatteryFlag 枚举。

class SYSTEM_POWER_STATUS{    public ACLineStatus ACLineStatus;    public BatteryFlag BatteryFlag;    // can be a value in the range 0 to 100, or 255 if status is unknown    public byte BatteryLifePercent;           public byte Reserved1;            // reserved; must be zero    // number of seconds of battery life remaining,     // or –1 if remaining seconds are unknown    public Int32 BatteryLifeTime;             // number of seconds of battery life when at full charge,     // or –1 if full battery lifetime is unknown        public Int32 BatteryFullLifeTime; }

SYSTEM_POWER_STATUS 是从 GetSystemPowerStatus 函数返回的。通过以下导入使其对应用程序可用:
[DllImport(“Kernel32.DLL”, CharSet=CharSet.Auto, SetLastError=true)][return: MarshalAs(UnmanagedType.Bool)]static extern bool GetSystemPowerStatus(    [In, Out] SYSTEM_POWER_STATUS SystemPowerStatus);
每当 WM_POWERBROADCAST 消息与 PBT_APMPOWERSTATUSCHANGE wParam 一同收到时,就调用此函数。此 wParam 表示系统电源中的某些部分已更改,GetSystemPowerStatus 函数对应用程序授予访问该信息的权限。该信息在传入的 SYSTEM_POWER_STATUS 内部返回。

 

在 Windows XP 上初始化依赖关系属性
如“检测操作系统”部分所述,加载应用程序时需初始化依赖关系属性以对其进行最新(请参见图 5)。在 Windows Vista 中不必执行此操作,因为此操作系统会在应用程序注册电源通知时通过窗口消息了解系统的当前电源状态。但对 Windows XP 而言并不是这样,因此初始化是必需的。检测操作系统时,此初始化操作在 PowerAwareWindow_Loaded 函数中完成,如下所示:
void InitializeDependencyPropertiesForXP(){    if (GetSystemPowerStatus(this._xpPowerStatus))    {        this.RunningOnBattery =             (ACLineStatus.Battery == _xpPowerStatus.ACLineStatus);        this.RemainingBattery = _xpPowerStatus.BatteryLifePercent;    }    this.MonitorOn = true;    this.PowerPlan = PowerPlan.Automatic;}
此时,指出没有与 Windows Vista 通知类似的 Windows XP 监视器或电源计划通知是很重要的。因此,当 Windows XP 应用程序尝试获取这些值时,会返回默认值。并且,即使应用程序挂接 MonitorOnChanged 事件或 PowerPlanChanged 事件,初始化后也不会触发事件。

 

Windows Vista 电源结构
下面讲述 Windows Vista 电源结构。POWERBROADCAST_SETTING 是 Windows Vista 主电源结构:
struct POWERBROADCAST_SETTING{    public Guid PowerSetting;    public UInt32 DataLength;}
在有关结构的官方定义中,存在以下第三方成员表述:数据。如结构中所示,此项在大小上可能有所不同,可以是 GUID,也可以是 DWORD。因此,在应用程序内部声明是此结构的一种自定义形式,可获取以下数据类型(请注意,此结构与最初的固有结构布局不同,因此不能用于封送处理;仅用于使值可在强类型样式的应用程序内部继续传送):
struct my_POWERBROADCAST_SETTING{    public IntPtr wParam;    public Guid PowerSetting;    public int PowerChangeData;    public Guid PowerPlanGuid;}
此结构还包括 wParam,以便使用此自定义形式时,可以将所有相关数据从窗口消息处理程序传递到同一结构中应用程序的 Windows Vista 消息处理程序(稍后定义)。
因此,当与 PBT_POWERSETTINGCHANGE 的 wParam 一同收到 Windows Vista 上的 WM_POWERBROADCAST 窗口消息时,与消息关联的 lParam 是 POWERBROADCAST_SETTING 结构的一个指针,其中包含有关系统电源状态更改的信息。
信息的最后一个要点是 Windows Vista 如何定义电源计划。“最大电源节省量”涉及一些非常有效的省电措施,有助于延长电池寿命:
static Guid GUID_MAX_POWER_SAVINGS =     new Guid(“a1841308-3541-4fab-bc81-f71556f20b4a”);
“无省电模式”几乎不使用任何省电措施:
static Guid GUID_MIN_POWER_SAVINGS =     new Guid(“8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c”);
最后,典型的“省电模式”电源计划使用的是相对有效的省电措施:
static Guid GUID_TYPICAL_POWER_SAVINGS =     new Guid(“381b4222-f694-41f0-9685-ff5bb260df2e”);
上述省电模式分别对应 PowerPlan.PowerSaver、PowerPlan.HighPerformance 和 PowerPlan.Automatic。如前所述,在 Windows Vista 用户界面中,它们分别称为“节能程序”、“高性能”和“已平衡”。

 

如何处理消息
图 10 中的代码使您可以深入了解 MessageProc 函数。此处有两个需要解释的要点。第一点是如何将任务发送至每个处理程序。第二点是在 Windows Vista 上出现 WM_POWERBROADCAST 消息时如何(以及何时)获取数据。

IntPtr MessageProc(    IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled){    switch (msg)    {        case WM_POWERBROADCAST:            if (_osVersion >= VISTA_OS_VERSION)            {                // Grab the relevant power data from the message here.                 // Then send the data and work to the Windows Vista                // handler here.            }            else            {                //  Send work to the Windows XP handler here.            }            break;        // Other (non-power) window messages in which the        // application is interested would be processed here        // (if the application needs to do so).    }    return IntPtr.Zero;}

通常,向每个处理程序发送任务就像调用示例代码中定义的函数一样简单。当应用程序在 Windows XP 或 Windows Vista 上运行时,这些函数会对消息进行适当处理。即使这样,也还是要采取一些预防措施以防止系统挂起。例如,如果处理消息的函数是占用大量处理器资源的复杂函数,则不会立即将控制权返回到窗口消息处理程序,这会导致出现明显的系统挂起。要避免此类挂起,应通过传送至 Dispatcher 线程将任务发送到 Windows Vista 和 Windows XP 处理程序(请参见图 11)。

delegate void VoidXPDelegate(/*power data arguments*/ );delegate void VoidVistaDelegate(/*power data arguments*/ );// A call to the Windows XP handler would look like this: this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,     new VoidXPDelegate(/*XP handler function here*/),     /*power data here*/);// A call to the Windows Vista handler would look like this: this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,     new VoidVistaDelegate(/*Vista handler function here*/),     /* power data here*/);

消息会在与 Dispatcher 关联的线程上以指定的优先级异步处理;在任务传送至 Dispatcher 后,会立即将控制权返回到 MessageProc 函数。有关 Dispatcher 和 Windows Presentation Foundation 线程的更多信息,请参阅 WPF 基础线程模型文档。
接下来介绍为 Windows Vista 电源事件捕获数据的主题。如前所述,WM_POWERBROADCAST 窗口消息的 wParam 部分对理解消息的本质来说是至关重要的。在 Windows XP 上,对于成功处理电源消息来说,这是很有必要的。但在 Windows Vista 上,此 wParam 可表明消息中的 lParam 是指向确定消息本质的数据的指针。图 12 显示了我如何复制 lParam 指针外的数据。您可以在此处看到 MessageProc 函数及这些更改。

// First grab the wParam, that’s important information to the // Windows Vista handler no matter whatmy_POWERBROADCAST_SETTING myPBS = new my_POWERBROADCAST_SETTING();myPBS.wParam = wParam;if (wParam == (IntPtr)PBT_POWERSETTINGCHANGE){    // Grab the power change information from the pointer passed    // in the window message    POWERBROADCAST_SETTING ps = (POWERBROADCAST_SETTING)        Marshal.PtrToStructure(lParam, typeof(POWERBROADCAST_SETTING));    // Copy the PowerSetting in the POWERBROADCAST_SETTING to     // our own structure    myPBS.PowerSetting = ps.PowerSetting;    // lParam points to the POWERBROADCAST_SETTING, but we need to     // calculate a pointer to where the data about the setting lives.     IntPtr pData = (IntPtr)((int)lParam + Marshal.SizeOf(ps));    // Use the pointer calculated to grab the relevant data. Note that    // the relevant data can be either a DWORD or a GUID, depending on    // the datalength indicated in the POWERBROADCAST_SETTING struct.       if (ps.DataLength == Marshal.SizeOf(typeof(Int32)))    {        myPBS.PowerChangeData = (Int32)Marshal.PtrToStructure(            pData, typeof(Int32));    }    else if (ps.DataLength == Marshal.SizeOf(typeof(Guid)))    {        myPBS.PowerPlanGuid = (Guid)Marshal.PtrToStructure(            pData, typeof(Guid));    }}// At this point ‘myPBS’ contains all the data the Windows Vista handler // needs for this application to process the system power change 

获得与消息相关的数据后,您需要更新相关属性并触发相关事件。考虑迄今为止已完成的所有任务,以获取通知并设置自定义依赖关系属性。实际上,此步骤非常简单。该步骤涉及的内容无非是以下两方面:向适当的应用程序变量分配操作系统提供的数据,或触发适当的自定义事件。

 

使用依赖关系属性和事件
我们现在来看一个在 PowerAwareWindow 中托管的应用程序。在此级别上,感知电源在 Windows Presentation Foundation 模型中非常适用,而创建既能在 Windows XP 上运行又能在 Windows Vista 上运行的感知电源应用程序也非常简单。
按 Window1.xaml 中的定义,首先要检查的是用户界面。请注意应用程序位于 Power- AwareWindow 内:
<local:PowerAwareWindow x:Class=”WPFPower_Aware.Window1”    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”    xmlns:local=”clr-namespace:WPFPower_Aware”    x:Name=”MainWindow”       Title=”WPF Power Aware Application” Height=”480” Width=”550”     Loaded=”Window1_Loaded” >    ...</local:PowerAwareWindow>
因此,XAML 文件可利用自定义依赖关系属性进行数据绑定。例如,我的示例中包含窗口底部的状态栏。状态栏内部有两个与电源相关的数据部分:文本块和进度条,前者指示当前系统电源,后者指示当前剩余电池电量百分比。此处分配的数据均与对应的依赖关系属性进行了数据绑定。
您会发现数据绑定非常简单。以进度条为例:
<ProgressBar IsIndeterminate=”False” x:Name=”myBatteryProgressBar”     Width=”200” Height=”20”    Value=”{Binding ElementName=MainWindow,             Path=RemainingBattery, Mode=OneWay}” />
使用这一小段标记,进度条就能始终保持最新,并且不需要源代码的干预。您甚至可以对自己的自定义控件(如电池充放电用的计时器)进行数据绑定,以显示此信息!
电源文本块稍微复杂一些,因为它涉及到文本块从 Boolean 值 (RunningOnBattery) 转换到字符串。要查看有关此内容的详细信息,请参考示例代码。

 

控制动画
如果您运行示例应用程序,就会看到两个动画元素。左侧的动画在源代码中进行控制,另一个动画在 XAML 中进行控制。当 RunningOnBattery 属性为 false 和 true 时,由 XAML 控制的动画相应为启动和停止。
图 13 中的代码段供动画使用,它通过在 XAML 中的使用展示自定义依赖关系属性的作用。使用此 XAML 段,无需代码干预即可停止和启动动画。而这仅仅是开始,您还可以在 XAML 中对动画和触发器实现更多功能。有关详细信息,请参见 Windows Presentation Foundation 动画概述和触发器类文档。

<Rectangle Name=”rect” Width=”140” Height=”90” Fill=”Blue” Stroke=”Red”>  <Rectangle.RenderTransform>    <ScaleTransform/>  </Rectangle.RenderTransform>  <Rectangle.Style>    <Style>      <Style.Triggers>        <Trigger Property=”local:PowerAwareWindow.RunningOnBattery”  Value=”False”>          <Trigger.EnterActions>            <BeginStoryboard Name=”myStoryboard”>              <Storyboard>                <DoubleAnimation Storyboard.TargetProperty=                  “(Rectangle.RenderTransform).(ScaleTransform.ScaleY)”                  From=”1” To=”1.5” RepeatBehavior=”Forever”                   AutoReverse=”True”/>              </Storyboard>            </BeginStoryboard>          </Trigger.EnterActions>          <Trigger.ExitActions>            <StopStoryboard BeginStoryboardName=”myStoryboard”/>          </Trigger.ExitActions>        </Trigger>      </Style.Triggers>    </Style>  </Rectangle.Style></Rectangle>

如前所述,示例应用程序中的左侧元素具有通过源代码控制的动画。当发生相关的系统电源事件时,系统的当前电源状态用于确定是否应暂停动画以省电。首先,应设置动画,然后挂接自定义事件。执行此操作所需的代码位于 Window1 .xaml.cs 中(请参见图 14)。

DoubleAnimation myDoubleAnimation;Storyboard myStoryboard;void Window1_Loaded(object sender, RoutedEventArgs e){    // Initialize the animation    myDoubleAnimation = new DoubleAnimation();    myDoubleAnimation.From = 0.0;    myDoubleAnimation.To = 360.0;    myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(10));    myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;    // Note that yRotate is an AxisAngleRotation3D declared    // in the Window1.xaml file    Storyboard.SetTargetName(myDoubleAnimation, “yRotate”);    Storyboard.SetTargetProperty(myDoubleAnimation,         new PropertyPath(AxisAngleRotation3D.AngleProperty));    myStoryboard = new Storyboard();    myStoryboard.Children.Add(myDoubleAnimation);    // Begin the animation if appropriate    if (!ShouldStopAnimation()) myStoryboard.Begin(this, true);    // Hook up the events from the PowerAwareWindow    this.RemainingBatteryChanged +=         new DependencyPropertyChangedEventHandler(            Window1_RemainingBatteryChanged);    this.RunningOnBatteryChanged +=         new DependencyPropertyChangedEventHandler(            Window1_RunningOnBatteryChanged);    this.SystemSuspending += new RoutedEventHandler(        Window1_SystemSuspending);    this.SystemResuming += new RoutedEventHandler(        Window1_SystemResuming);    this.MonitorOnChanged +=         new DependencyPropertyChangedEventHandler(            Window1_MonitorOnChanged);    this.PowerPlanChanged +=         new DependencyPropertyChangedEventHandler(            Window1_PowerPlanChanged);}

应用程序应如何及何时采取省电措施的决定在很大程度上取决于应用程序的行为和需求。在本示例中,将按以下方式做出决定,并且电源事件处理程序会在合适的事件处理程序中利用此函数:
bool ShouldStopAnimation(){    // If the monitor is off, or if we’re running on a battery    // and the user hasn’t explicited requested high performance,    // stop the animation    return        !MainWindow.MonitorOn ||        (MainWindow.PowerPlan != PowerPlan.HighPerformance &&          MainWindow.RunningOnBattery); }
在此示例中有一点需要注意:在用户界面的列表框中已报告了电源事件。例如,这是 RunningOnBatteryChanged 事件处理程序:
void Window1_RunningOnBatteryChanged(    object sender, DependencyPropertyChangedEventArgs e){    myEventsListBox.Items.Add(        “RunningOnBattery changed - now it’s {0}”, (bool)e.NewValue);    if (ShouldStopAnimation()) myStoryboard.Pause(this);    else myStoryboard.Resume(this);}
通过查看移动 PC 电源文档和设备感知文档,您可以了解可供应用程序使用的其他省电措施的详细信息。

 

结束语
本文及文中随附的代码提供了一个起点,您可以从中了解如何使 Windows Presentation Foundation 应用程序具有感知电源功能。请注意,除了我在此处介绍的内容以外,还需要了解很多有关感知电源及 Windows Presentation Foundation 的内容。因此,我强烈建议您在设计感知电源应用程序前,查看本文提到的所有资源。您会发现更多可帮助您为程序定制最佳解决方案的功能和技术。
posted on 2010-06-24 14:23  李sir  阅读(590)  评论(0)    收藏  举报