WPF:获取 Windows 7 电源管理系统的数据





5.00/5 (2投票s)
在我看来,当今开发者面临的一个重大挑战是充分利用操作系统提供的所有优势。
引言
大家好!
在我看来,当今开发者面临的一个重大挑战(以及许多其他挑战)是充分利用操作系统提供的所有优势,并努力使我们的应用程序也能模仿这些优势,从而尽可能模糊操作系统和应用程序之间的界限;这样,我们的用户在使用我们的应用程序时会更轻松,因为使用方式和行为方式会更加熟悉。
从这个意义上说,Windows 7 是一个突破,它提供了一系列我们可以集成到应用程序中的功能,例如电源管理、跳转列表、带工具栏的缩略图、任务栏图标上的进度条等等。
我将写一些关于这些问题的文章。Windows 7 是历史上采用最快的操作系统,已售出约 1.5 亿份许可证。这些数字使得我们可以关注其特性;这对我们来说是一个明智的选择,对我们的应用程序和用户都具有巨大优势。
我决定从电源管理开始。笔记本电脑在商业领域越来越普及,很快我们就会看到配备 Windows 7 的平板电脑爆炸式增长,因此我们的应用程序将开始迁移到这些新的场景,在这些场景中,有一点至关重要:电池续航时间。
我们需要让我们的应用程序更节能;我们必须明白,桌面和笔记本电脑/平板电脑是两种不同的场景,并尽量避免在短时间内耗尽所有电池电量。让我们的应用程序了解其运行设备上的电池当前状态,可以根据可用能源的百分比来实现,我们甚至可以建议用户更改应用程序的使用方式来最大化电池续航时间。
开始工作
首先,创建一个新的 WPF 应用程序项目(文件 > 新建 > 项目);创建后,我们将在 MainWindow.xaml 文件中设计主屏幕。
我在本应用中使用的样式可在下载文件 Application.xaml 中找到;如果您对如何使用/创建样式有疑问,请参阅我关于此主题的两篇文章:此处和此处。
使用的控件非常基础。右上角的电池是一个进度条,文本块用于显示文本。有两个复选框、一个选项卡控件和网格用于对所有内容进行一些排序。这是 XAML 代码:
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="Black">
</GradientStop>
<GradientStop Offset="1" Color="DarkGray">
</GradientStop>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="BateriaPercent" FontSize="24"
Text="BaterÃa restante (%):" VerticalAlignment="Center"
HorizontalAlignment="Left" Foreground="White"
Margin="6,0,0,0"/>
<ProgressBar Height="40" Margin="245,5,12,0"
Name="pBarEnergia"
VerticalAlignment="Top" Value="0" />
<TabControl Grid.Row="1" Margin="12,6,12,12"
Name="TabControl1">
<TabItem Header="Información de energÃa"
Name="TabItem1">
<Grid>
<Grid.Background>
<ImageBrush
ImageSource="pack://application:,,, /
WPF Power Management;component/Info.png"
Stretch="Uniform" Opacity=".2"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".4*"></ColumnDefinition>
<ColumnDefinition Width=".6*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height=".1*"></RowDefinition>
<RowDefinition Height=".1*"></RowDefinition>
<RowDefinition Height=".1*"></RowDefinition>
<RowDefinition Height=".7*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Name="tbFuente" Text="Fuente de energÃa:"
VerticalAlignment="Center" HorizontalAlignment="Left"
Foreground="White" Margin="6,0,0,0"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Name="tbFuenteText" VerticalAlignment="Center"
HorizontalAlignment="Left" Foreground="White"
Margin="6,0,0,0"/>
<TextBlock Grid.Row="1" Grid.Column="0"
Name="tbBateriaPresent" Text="BaterÃa presente:"
VerticalAlignment="Center" HorizontalAlignment="Left"
Foreground="White" Margin="6,0,0,0"/>
<CheckBox Grid.Row="1" Grid.Column="1"
Name="chkBateriaPresente"
HorizontalAlignment="Left"
Height="20"
VerticalAlignment="Center"></CheckBox>
<TextBlock Grid.Row="2" Grid.Column="0"
Name="tbUPSPresent" Text="UPS presente:"
VerticalAlignment="Center" HorizontalAlignment="Left"
Foreground="White" Margin="6,0,0,0"/>
<CheckBox Grid.Row="2" Grid.Column="1"
Name="chkUpsPresente" HorizontalAlignment="Left"
Height="20" VerticalAlignment="Center"></CheckBox>
<TextBlock Grid.Row="3" Grid.Column="0"
Name="tbEstadoBateria" Text="Estado de la baterÃa:"
VerticalAlignment="Top" HorizontalAlignment="Left"
Foreground="White" Margin="6,0,0,0"/>
<TextBlock Grid.Row="3" Grid.Column="1"
Name="tbEstado" VerticalAlignment="Top"
HorizontalAlignment="Left" Foreground="White"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
仔细看,你会发现它并不复杂。
为了处理能源系统,我们使用 Windows 7 的 Win32(非托管)API,因此最好创建一个单独的类来处理导入和调用 API 中所需函数的所有工作,并返回我们需要的信息。
为此,我们需要 InteropServices
,因此最好在此新类中导入此命名空间。
Imports System.Runtime.InteropServices
Public Class PowerManagement
'...
'...
'...
End Class
现在我们开始定义必要的常量,以便使此类中的代码更具可读性。
'This constant number mean that access to the power management info is
'denied with our current user.
Const STATUS_ACCESS_DENIED As UInteger = 3221225506
'When Call for info, this number mean that we want to retrieve the
'power capabilities of the computer.
Const SYSTEM_POWERCAPABILITIES As Integer = 4
'When call for info, this number mean that we want to retrieve the
'actual status and info of the battery.
Const SYSTEM_BATTERYINFO As Integer = 5
然后我们定义两个结构,它们包含 CallNtPowerInformation
方法返回的值。
第一个称为 SystemPowerCapabilities
,包含系统电源功能的全部信息。
<StructLayout(LayoutKind.Sequential)>
Structure SystemPowerCapabilities
<MarshalAs(UnmanagedType.I1)>
Public PowerButtonPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public SleepButtonPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public LidPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public SystemS1 As Boolean
<MarshalAs(UnmanagedType.I1)>
Public SystemS2 As Boolean
<MarshalAs(UnmanagedType.I1)>
Public SystemS3 As Boolean
<MarshalAs(UnmanagedType.I1)>
Public SystemS4 As Boolean
<MarshalAs(UnmanagedType.I1)>
Public SystemS5 As Boolean
<MarshalAs(UnmanagedType.I1)>
Public HiberFilePresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public FullWake As Boolean
<MarshalAs(UnmanagedType.I1)>
Public VideoDimPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public ApmPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public UpsPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public ThermalControl As Boolean
<MarshalAs(UnmanagedType.I1)>
Public ProcessorThrottle As Boolean
Public ProcessorMinThrottle As Byte
Public ProcessorMaxThrottle As Byte
<MarshalAs(UnmanagedType.I1)>
Public FastSystemS4 As Boolean
Public spare2_1 As Byte
Public spare2_2 As Byte
Public spare2_3 As Byte
<MarshalAs(UnmanagedType.I1)>
Public DiskSpinDown As Boolean
Public spare3_1 As Byte
Public spare3_2 As Byte
Public spare3_3 As Byte
Public spare3_4 As Byte
Public spare3_5 As Byte
Public spare3_6 As Byte
Public spare3_7 As Byte
Public spare3_8 As Byte
<MarshalAs(UnmanagedType.I1)>
Public SystemBatteriesPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public BatteriesAreShortTerm As Boolean
Public granularity As Integer
Public capacity As Integer
End Structure
正如您所见,还有更多信息是我选择不显示的;我们可以真正找到一种方法来控制能耗以及我们所处设备在电源方面的技术能力。
结构装饰器 <StructureLaout(LayoutKind.Sequential)>
表示同一结构中的字段将按我们在此处表示的顺序连续排列。字段装饰器 <MarshalAs(UnmanagedType.I1)>
表示互操作性应如何处理数据类型(在这种情况下全部为 Boolean
),以方便托管数据类型和非托管数据类型之间的信息转换。
第二个结构是 SystemBatteryState
,包含我们机器电池的全部信息。
<StructLayout(LayoutKind.Sequential)>
Structure SystemBatteryState
<MarshalAs(UnmanagedType.I1)>
Public AcOnLine As Boolean
<MarshalAs(UnmanagedType.I1)>
Public BatteryPresent As Boolean
<MarshalAs(UnmanagedType.I1)>
Public Charging As Boolean
<MarshalAs(UnmanagedType.I1)>
Public Discharging As Boolean
Public spare1 As Byte
Public spare2 As Byte
Public spare3 As Byte
Public spare4 As Byte
Public MaxCapacity As UInteger
Public RemainingCapacity As UInteger
Public Rate As UInteger
Public EstimatedTime As UInteger
Public DefaultAlert1 As UInteger
Public DefaultAlert2 As UInteger
End Structure
它比前一个要小得多,并且使用了相同的装饰器类型。
最后,我们需要从负责电源管理任务的 Win32 DLL 中导入所需的函数。
<DllImport("powrprof.dll", SetLastError:=True)>
Private Shared Function CallNtPowerInformation(
ByVal InformationLevel As Int32,
ByVal lpInputBuffer As IntPtr,
ByVal nInputBufferSize As UInt32,
ByVal lpOutputBuffer As IntPtr,
ByVal nOutputBufferSize As UInt32) As UInt32
End Function
CallNtPowerInformation
函数是我们用来与 Windows 7 通信并获取所有所需能源数据的函数。它有五个参数;第二个和第三个参数不重要;由于我们不想发送信息,只想接收,因此第二个参数为 Nothing
,第三个参数为 0;第一个参数是我们使用上面定义的两个常量(SYSTEM_
)来告诉函数我们要请求哪种类型的信息;第四个参数是指向我们要存储接收信息的变量的指针;第五个参数是变量的当前大小。
好了,现在我们已经声明了所有内容,我们需要处理 Win32 API;我们将在该类中创建两个新方法,负责获取我们想要的信息。
首先,我们创建 GetPowerCapabilities
方法,代码如下:
Public Shared Function GetPowerCapabilities() As SystemPowerCapabilities
Dim PowerCapabilities As SystemPowerCapabilities
Dim Status As IntPtr = IntPtr.Zero
Dim ReturnValue As UInteger
Try
Status = Marshal.AllocCoTaskMem(Marshal.SizeOf(GetType(SystemPowerCapabilities)))
ReturnValue = CallNtPowerInformation(SYSTEM_POWERCAPABILITIES,
Nothing,
0,
Status,
Marshal.SizeOf(GetType(SystemPowerCapabilities)))
If ReturnValue = STATUS_ACCESS_DENIED Then
MessageBox.Show("The user doesnt have access rights to get Power information.")
Return Nothing
End If
PowerCapabilities = Marshal.PtrToStructure(Status, GetType(SystemPowerCapabilities))
Catch ex As Exception
Finally
Marshal.FreeCoTaskMem(Status)
End Try
Return PowerCapabilities
End Function
我们将仔细审查此方法中的代码;如果您以前没有使用过 .NET 中的 Win32 函数,您会发现一些奇怪的东西,特别是那些以 Marshal
开头的。
Marshal
是 InteropServices
命名空间中的一个类,提供给我们用于处理非托管方法;它非常完整,有很多方法;在我们的例子中,我们使用了四个:
Marshal.AllocCoTaskMem
:用于从指定大小的 COM 任务分配器中预留一块内存,并返回指向分配块的指针。Marshal.FreeCoTaskMem
:使用此方法,我们释放之前使用AllocCoTaskMem
方法获得的内存块。Marshal.PtrToStructure
:将指针指向的内存块的内容复制到一个结构中,以便访问数据。Marshal.SizeOf
:返回对象的大小(以字节为单位);无论托管类型的开销如何,它都返回非托管大小,这是非托管函数所必需的。
在此函数中,我们使用了我们声明的第三个常量(STATUS_ACCESS_DENIED
);如果对 CallNtPowerInformation
的调用返回此值,则意味着我们的用户无权进行此调用。
最后,我们必须创建第二个方法来与 win32 API 交互,即 GetBatteryInformation
。
Public Shared Function GetBatteryInformation() As SystemBatteryState
Dim Status As IntPtr = IntPtr.Zero
Dim BattStatus As SystemBatteryState
Dim ReturnValue As UInteger
Try
Status = Marshal.AllocCoTaskMem(Marshal.SizeOf(GetType(SystemBatteryState)))
ReturnValue = CallNtPowerInformation(SYSTEM_BATTERYINFO,
Nothing,
0,
Status,
Marshal.SizeOf(GetType(SystemBatteryState)))
If ReturnValue = STATUS_ACCESS_DENIED Then
MessageBox.Show("The user doesn´t have access rights to get Power information.")
Return Nothing
End If
BattStatus = Marshal.PtrToStructure(Status, GetType(SystemBatteryState))
Catch ex As Exception
Finally
Marshal.FreeCoTaskMem(Status)
End Try
Return BattStatus
End Function
代码基本相同;如果您仔细观察,唯一的变化是第一个 CallNtPowerInformation
参数以及我们想要获取的结构类型;在这种情况下,我们得到的是系统电池的所有详细信息,最大充电量、当前充电量、电源...等。
好了,至此,我们已经完成了 Win32 API 的工作。现在我们只需要在 WPF 主窗口中编写一些代码来实现这一切。
让我们从变量声明开始。我们需要在类中定义三个全局私有变量。
Private MySPC As PowerManagement.SystemPowerCapabilities
Private MyBatt As PowerManagement.SystemBatteryState
Private WithEvents Tmr As New DispatcherTimer
基本上,我们为所需的每个结构定义一个变量:SystemBatteryState
和 SystemPowerCapabilities
,以及负责不断刷新能源系统信息的 DispatcherTimer
控件。
在 Load
事件中,配置计时器并检查我们正在运行的操作系统版本;请注意,此代码是为 Windows 7 编写的,在 XP 或 Vista 上可能在某些地方失败。
If Environment.OSVersion.Version.Major = 6 And
Environment.OSVersion.Version.Minor > 0 Then
Tmr.Interval = New TimeSpan(0, 0, 1)
Tmr.IsEnabled = True
Else
MessageBox.Show("You need Windows 7 at least.")
End If
这是一个非常简单的代码,尤其是与我们上面看到的令人头疼的 Win32 互操作代码相比,哈哈。我们正在运行 Windows 7,尽管名称如此,但它是操作系统版本 6.1,所以我们看到它大于 6.0。下一版 Windows 可能会支持此代码,因此最好找到一个版本大于 6.0(Windows Vista)的版本。完成此操作后,我们将计时器间隔设置为 1 秒并启用它。
DispatcherTimer
控件不像通常的 Windows Forms Timer 那样触发 Elapsed
事件;在这种情况下,我们处理 Tick
事件。
Private Sub Tmr_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Tmr.Tick
Tmr.IsEnabled = False
MySPC = PowerManagement.GetPowerCapabilities()
'Test if we have Battery in our machine!
If MySPC.SystemBatteriesPresent = True Then
SetValues()
End If
Tmr.IsEnabled = True
End Sub
收到事件后,我们首先关闭计时器。在完成之前,我们不想再次触发。我们调用 GetPowerCapabilities
方法,并检查您的计算机是否包含电池,如果包含,则调用 setValue
方法,该方法负责将值传递给我们的控件进行显示,最后重新启用计时器以运行其正常的刷新周期。
所需的最后一个方法,setValue
,只是获取必要的能源值并将它们传递给我们在应用程序中进行可视化。
Private Sub SetValues()
tbFuenteText.Text = If(MyBatt.AcOnLine = True, "AC Line", "Battery")
chkBateriaPresente.IsChecked = MySPC.SystemBatteriesPresent
chkUpsPresente.IsChecked = MySPC.UpsPresent
'Get the battery information
MyBatt = PowerManagement.GetBatteryInformation()
pBarEnergia.Value = Math.Round((MyBatt.RemainingCapacity*100)/MyBatt.MaxCapacity,0)
tbEstado.Text = "AC Line: " + If(MyBatt.AcOnLine = True, "YES", "NO") + vbCrLf +
"Max Capacity: " + MyBatt.MaxCapacity.ToString + " mWh" + vbCrLf +
"Actual Capacity: " + MyBatt.RemainingCapacity.ToString + "mWh" + vbCrLf +
"Minimum charge alert: " + MyBatt.DefaultAlert2.ToString + "mWh" + vbCrLf +
"Critical level alert: " + MyBatt.DefaultAlert1.ToString + "mWh"
End Sub
这样,如果我们编译我们的应用程序(并且您正在 Windows 7 上使用笔记本电脑或平板电脑工作),您应该会看到文章开头显示的所有信息。
感谢阅读,祝您编码愉快!
历史
- 2010 年 7 月 19 日 - 初版。