65.9K
CodeProject 正在变化。 阅读更多。
Home

Vista 中的 C++ 实用功能:监视计算机电源状态

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2006年10月5日

7分钟阅读

viewsIcon

110780

downloadIcon

2473

如何使用电源状态通知,使您的应用程序在必要时节省电力。

目录

引言

在这篇 Vista 实用功能文章中,我将演示如何使用 Vista 中新增的通知来监控计算机的电源状态。对于可能密集占用 CPU 或图形资源的应用程序(如音频编码器、游戏和媒体播放器),监控电源状态的能力使它们在笔记本电脑上运行时能够智能地管理功耗。例如,媒体播放器可能会在电池电量低于 15% 时自动停止播放,这样用户就有充足的时间连接电源线,而不必匆忙关闭或停止播放器来节省剩余的少量电量。

本文使用 Visual Studio 2005、WTL 7.5Windows SDK 针对 Vista RTM 版本编写。有关下载这些组件的更多信息,请参阅“第一个 Vista 实用功能文章中的介绍”。

本文的示例项目是一个基于对话框的应用程序,它显示了一些简单的动画。它会监控计算机的电源状态,更新显示以反映当前状态,并在需要节能的情况下关闭动画。例如,以下对话框显示计算机正在使用交流电源运行:

在这种情况下,“Hello, Bob!”文本每秒会改变颜色,因为计算机不处于低功耗状态,可以使用动画。

Vista 中的电源方案和电源

Vista 定义了三个默认的电源方案,分别名为“节能”、“平衡”和“高性能”。每种方案都是一组预设的设置,用于控制硬盘旋转关闭时间、使用的最大 CPU 速度等。您可以通过电池托盘图标或“电源选项”控制面板小程序来选择电源方案。下面是选择了“节能”方案的托盘选择器:

Vista 还有一些设置,当计算机使用不同的电源时可以有所不同:电池与交流电源。例如,您可以设置显示器在使用电池时 3 分钟后关闭,或使用交流电源时 15 分钟后关闭。Vista 还监控剩余的电池电量,其百分比范围从 1 到 100。

注册电源通知

应用程序可以注册以接收有关电源方案、电源或电池电量更改的通知。涉及的两个 API 是 RegisterPowerSettingNotification()UnregisterPowerSettingNotification()RegisterPowerSettingNotification() 的原型是:

HPOWERNOTIFY RegisterPowerSettingNotification(
    HANDLE hRecipient, LPCGUID PowerSettingGuid, DWORD Flags);

hRecipient 是希望接收通知的对象的句柄;在这种情况下,它是我们的对话框的 HWNDPowerSettingGuid 是一个 GUID,指示我们希望接收哪个事件的通知。Flags 指示 hRecipient 的句柄类型;在此示例代码中,句柄始终是 HWND,因此我们将为 Flags 传递 DEVICE_NOTIFY_WINDOW_HANDLE

PowerSettingGuid 的相关 GUID 是:

  • GUID_POWERSCHEME_PERSONALITY:注册电源方案的更改
  • GUID_ACDC_POWER_SOURCE:注册电源的更改
  • GUID_BATTERY_PERCENTAGE_REMAINING:注册电池电量的更改

RegisterPowerSettingNotification() 返回一个 HPOWERNOTIFY,如果传递的参数无效则返回 NULLHPOWERNOTIFY 仅在注销时使用;实际的通知通过窗口消息发生。

示例应用程序如何注册

CMainDlg 有三个 HPOWERNOTIFY 成员:

  HPOWERNOTIFY m_hPowerSchemeNotify, m_hPowerSourceNotify,
               m_hBatteryPowerNotify;

OnInitDialog() 为每种类型的通知调用一次 RegisterPowerSettingNotification()。例如,此代码注册有关电源方案更改的通知:

  m_hPowerSchemeNotify = RegisterPowerSettingNotification (
                           m_hWnd, &GUID_POWERSCHEME_PERSONALITY,
                           DEVICE_NOTIFY_WINDOW_HANDLE );
 
  if ( NULL == m_hPowerSchemeNotify )
    ATLTRACE("Failed to register for notification of power scheme changes!\n");

如果 RegisterPowerSettingNotification() 失败,这并非严重错误;应用程序只是不会收到该特定事件的通知。

处理电源通知

当应用程序需要接收电源更改通知时,系统会向其发送一个 WM_POWERBROADCAST 消息。WM_POWERBROADCAST 不是新消息,但在 Vista 中有一个新事件代码 PBT_POWERSETTINGCHANGE 以及一个相关的结构 POWERBROADCAST_SETTING。当发送 PBT_POWERSETTINGCHANGE 通知时,消息的 lParam 指向一个 POWERBROADCAST_SETTING 结构,其外观如下:

typedef struct {
  GUID PowerSetting;
  DWORD DataLength;
  UCHAR Data[1];
} POWERBROADCAST_SETTING;

PowerSetting 是一个 GUID,指示发生了哪种类型的事件。此成员的可能值与传递给 RegisterPowerSettingNotification() 的 GUID 相同。结构的其余部分是一个可变长度的数据块,其中包含事件的详细信息。这些数据因事件而异:

PowerSetting

Data 描述

GUID_POWERSCHEME_PERSONALITY

指示活动电源方案的 GUID

  • GUID_MAX_POWER_SAVINGS:“节能”方案
  • GUID_MIN_POWER_SAVINGS:“高性能”方案
  • GUID_TYPICAL_POWER_SAVINGS:“平衡”方案

GUID_ACDC_POWER_SOURCE

一个 int,指示电源:0 为交流电源,1 为电池,或 2 为短期电池(例如,UPS 中的电池)。

GUID_BATTERY_PERCENTAGE_REMAINING

一个 int,指示剩余电池电量:1 到 100。

示例应用程序如何处理通知

CMainDlg 有几个成员用于跟踪当前的电源情况:

  enum EPowerScheme { pwrPowerSaver, pwrMaxPerf, pwrBalanced };
 
  EPowerScheme m_eCurrPowerScheme;
  bool m_bOnBattery;
  int  m_nBatteryPower;
  bool m_bUseAnimation;

这些成员被设置为合理的默认值(平衡方案,未在电池上运行,动画开启),并在 WM_POWERBROADCAST 处理程序中更改。OnPowerBroadcast() 首先检查事件类型,如果不是 PBT_POWERSETTINGCHANGE 则返回:

LRESULT CMainDlg::OnPowerBroadcast ( DWORD dwEvent, DWORD dwData )
{
  // We only care about Vista power setting notifications.
  if ( PBT_POWERSETTINGCHANGE != dwEvent )
    {
    SetMsgHandled(false);
    return 0;
    }

接下来,我们获取随消息发送的 POWERBROADCAST_SETTING 结构的指针,并查看其 PowerSetting 成员以了解此通知针对的是哪个事件。如果是 GUID_POWERSCHEME_PERSONALITY,我们确定新的方案是什么。如果方案不是三个预定义的 GUID 之一,那么我们将假定方案为“平衡”。

POWERBROADCAST_SETTING* pps = (POWERBROADCAST_SETTING*) dwData;
 
  if ( sizeof(GUID) == pps->DataLength &&
       pps->PowerSetting == GUID_POWERSCHEME_PERSONALITY )
    {
    // This is a power scheme change notification
    GUID newScheme = *(GUID*)(DWORD_PTR) pps->Data;
 
    if ( GUID_MAX_POWER_SAVINGS == newScheme )
      {
      // New scheme: max power savings
      m_eCurrPowerScheme = pwrPowerSaver;
      m_cPowerScheme.SetWindowText ( _T("Power saver") );
      }
    else if ( GUID_MIN_POWER_SAVINGS == newScheme )
      {
      // New scheme: min power savings (max perf)
      m_eCurrPowerScheme = pwrMaxPerf;
      m_cPowerScheme.SetWindowText ( _T("Max performance") );
      }
    else if ( GUID_TYPICAL_POWER_SAVINGS == newScheme )
      {
      // New scheme: balanced
      m_eCurrPowerScheme = pwrBalanced;
      m_cPowerScheme.SetWindowText ( _T("Balanced") );
      }
    else
      {
      // Unrecognized scheme, we'll treat this like balanced
      m_eCurrPowerScheme = pwrBalanced;
      m_cPowerScheme.SetWindowText ( _T("Balanced") );
      }
    }

对于电源通知,我们确定新的电源是电池还是交流电源:

  else if ( sizeof(int) == pps->DataLength &&
            pps->PowerSetting == GUID_ACDC_POWER_SOURCE )
    {
    // This is a power source change notification
    int nPowerSrc = *(int*)(DWORD_PTR) pps->Data;
 
    m_bOnBattery = (0 != nPowerSrc);
    m_cPowerSource.SetWindowText ( m_bOnBattery ? _T("Battery") :
                                                  _T("AC power") );
    }

最后,对于电池电量通知,我们保存新的百分比:

  else if ( sizeof(int) == pps->DataLength &&
            pps->PowerSetting == GUID_BATTERY_PERCENTAGE_REMAINING )
    {
    // This is a battery power notification
    int nPercentLeft = *(int*)(DWORD_PTR) pps->Data;
    CString sPercentLeft;
 
    sPercentLeft.Format ( _T("%d"), nPercentLeft );
    m_cBatteryPower.SetWindowText ( sPercentLeft );
    m_nBatteryPower = nPercentLeft;
    }

现在我们已经更新了静态文本控件以反映新的电源状态,我们确定是否打开或关闭动画。应用程序的逻辑如下:

  1. 如果满足以下条件,则打开动画:
    • 当前方案为“高性能”,或者
    • 当前方案为“平衡”,并且计算机未在电池上运行。
  2. 如果满足以下条件,则关闭动画:
    • 当前方案为“平衡”,并且计算机在电池上运行,或者
    • 当前方案为“节能”,或者
    • 计算机在电池上运行,并且电池电量为 20% 或更低(无论当前方案如何)。

bool bUseAnimation =
         (pwrMaxPerf == m_eCurrPowerScheme) ||
         (pwrBalanced == m_eCurrPowerScheme && !m_bOnBattery);
 
  if ( m_bOnBattery && m_cBatteryPower <= 20 )
    bUseAnimation = false;
 
  // If the animation setting has changed, save the new setting
  // and redraw the message area.
  if ( bUseAnimation != m_bUseAnimation )
    {
    m_bUseAnimation = bUseAnimation;
    m_cMessage.RedrawWindow();
    }
 
  return TRUE;    // allow all power actions
}

然后 m_bAnimation 标志在其他地方用于确定如何绘制“Hello, Bob!”消息。下面分别显示了动画开启和关闭时的对话框外观:

结论

对于 CPU 密集型应用程序,了解计算机的电源状态对用户来说会非常有用。例如,CD 刻录应用程序可以在电池电量过低时警告用户,或者下载管理器可以在低功耗情况下停止下载,以防止无线网卡消耗过多电量。这些细微之处可以使您的应用程序在 Vista 上运行时比其他应用程序更具优势。

参考文献

版权和许可

本文是受版权保护的材料,© 2006 Michael Dunn。我知道这阻止不了人们在网上随意复制,但我还是必须说出来。如果您有兴趣翻译本文,请给我发电子邮件告知。我不认为我会拒绝任何人翻译的许可,只是希望知道翻译情况,以便在此处发布链接。

本文的演示代码已发布至公共领域。我之所以这样做是为了让代码能造福所有人。(我没有将文章本身设为公共领域,因为让文章仅在 CodeProject 上发布有助于提升我个人和 CodeProject 网站的可见度。)如果您在自己的应用程序中使用演示代码,发送一封电子邮件让我知道将不胜感激(只是为了满足我对人们是否受益于我的代码的好奇心),但并非强制要求。在您自己的源代码中注明出处也同样受欢迎,但并非强制要求。

修订历史

  • 2006 年 10 月 5 日:文章首次发布。
  • 2006 年 12 月 26 日:代码已在 Vista RTM 版本上测试,并相应地更新了介绍。

系列导航:« 在 UI 中使用 Glass | 使用新的 Vista 文件对话框 »

© . All rights reserved.