Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF: Getting data from the Windows 7 power management system

0.00/5 (No votes)
10 Aug 2010 1  
In my opinion, one of the big challenges developers have today is exploiting all the advantages offered by the Operating System.

App.PNG

Introduction

Hi all again!

In my opinion, one of the big challenges developers have today (among many others) is exploiting all the advantages offered by the Operating System and trying to make our application mimic the same so that the line of separation between the OS and the application becomes as blurred as possible; this way, our users will have less work when using our application, because the way you use it and how it behaves will be much more familiar.

Windows 7, in this sense, has been a breakthrough, by making available a number of features that we incorporate into our applications, like power management, jumplists, thumbnails with toolbars, progress bars in the task bar icon, and many others.

I will be writing some articles on these issues. Windows 7 is the most rapid adoption OS in history, with about 150 million licenses sold. These numbers made focus on its characteristics; it is a safe bet for us, and a great advantage to both our applications and users.

I decided to start with power management. More and more laptops are gaining ground in the business, and soon we will see an explosion in tablets equipped with Windows 7, so that our applications will begin to move to these new scenarios where one thing is premium against all: the battery life.

We need to make our applications energy friendly; we must understand that the desktop and the laptop / tablet are two different scenarios and try to avoid using all the battery life in a short time. Making our applications know the current status of the battery from the device on which they are running can be implemented in accordance with the % of available energy, and we can even suggest our users to alter the way they are using the application to maximize its life.

Begin to Work

First, create a new WPF Application project (File> New> Project); once created, we will design the main screen in the file MainWindow.xaml.

The styles that I have used in this application are available in the download in the file Application.xaml; if you have questions about how to use / create styles, take a look at my two articles on this topic: here and here.

The controls used are very basic. The battery in the upper right corner is a progressbar, and textblocks are for the text. There are two checkboxes, a tab control, and grids for sorting everything a bit. Here is the 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>

If you look carefully, you will see that it is not complicated.

To work with the energy system, we use the Windows 7 Win32 (unmanaged) APIs, so it is best to create a separate class to handle all the work to import and call the necessary functions from the API and return the information we need.

For this, we need InteropServices, so it's best to import this namespace in this new class:

Imports System.Runtime.InteropServices
Public Class PowerManagement
    '...
    '...
    '...
End Class

Now we begin to define the necessary constants to make our code in this class more readable:

'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

Then we define two structures that contain the values returned by the method CallNtPowerInformation.

The first is called SystemPowerCapabilities, and contains all the information with the power capabilities of the system:

<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

As you can see, there is much more information than I have chosen to show; we can really pinpoint a way to control energy consumption and the technical capabilities in terms of the power of the machine in which we live.

The structure decorator <StructureLaout(LayoutKind.Sequential)> indicates that the fields of the same will be ranked consecutively as we have expressed here. The field decorator <MarshalAs(UnmanagedType.I1)> indicates the way interop should work with the data type (Boolean in all cases) to facilitate the conversion of information between managed and unmanaged data types.

The second structure is SystemBatteryState, and contains all the information regarding the battery of our machine:

<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

This is much smaller than its predecessor, and uses the same types of decorators.

Finally, we need to import the required function from the Win32 DLLs that are in charge of the power management tasks:

<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

The function CallNtPowerInformation is the one we will use for communicating with Windows 7, and gets all the energy data we need. It has five parameters; the second and third parameters are not important; since we do not want to send information, only receive, the second ships with Nothing, and the third is sent 0; the first parameter is where we use the two constants defined above (SYSTEM_) to tell the function what type of information we are requesting; the fourth parameter is a pointer to the variable that we want to store the information received, and the fifth is the current size of the variable.

Well, now that we've done declaring everything, we need to work with the Win32 API; we will create two new methods in this class to be responsible for obtaining the information we want.

First, we create the method GetPowerCapabilities with this code:

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

We will carefully review the code in this method; if you have not worked before with Win32 functions from. NET, will see a couple of rare things, especially those that begin with Marshal.

Marshal is a class of the namespace InteropServices provided to us to work with unmanaged methods; it is very complete and has many methods; in our case, we use four:

  • Marshal.AllocCoTaskMem: Used to reserve a block of memory from the COM task allocator of the given size, and returns a pointer to the allocated block.
  • Marshal.FreeCoTaskMem: With this method, we release a memory block previously obtained with the method AllocCoTaskMem.
  • Marshal.PtrToStructure: Copies the contents of the memory block pointed to by the pointer that will indicate to a structure to access the data.
  • Marshal.SizeOf: Returns the size in bytes of an object; regardless of the overhead of managed types, you return the unmanaged size, which is the need for unmanaged functions.

In this function, we use the third constant that we declared (STATUS_ACCESS_DENIED); if the result of the call to CallNtPowerInformation returns this value, it means that our user does not have permission to make this call.

Finally, we must create the second method to interact with the 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

The code is basically the same; if you look closely, the only changes are the first CallNtPowerInformation parameter and the type of the structure we want to obtain; in this case, what we get is all the details about the system battery, maximum load, current load, power source...

Well, with this, we have completed the work with the Win32 API. Now we only need some code in our main window of WPF to make this all work.

Let's start with the declarations of the variables. We need three global private variables in the class:

Private MySPC As PowerManagement.SystemPowerCapabilities
Private MyBatt As PowerManagement.SystemBatteryState
Private WithEvents Tmr As New DispatcherTimer

Basically, we define a variable for each structure needed, SystemBatteryState and SystemPowerCapabilities, and the DispatcherTimer control that will be responsible for constantly refreshing the energy system information.

In the Load event, configure the timer and check the version of the Operating System on which we are running; note that this code is for Windows 7, and it is likely to fail at certain points in XP or 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

This is a very simple code, especially after the embarrassing Win32 Interop code we saw above, hehe. We are running Windows 7, which despite its name is the Operating System version 6.1, so we see that we are at greater than 6.0. It is likely that the next Windows will support this code, so the best thing is to find a version which exceeds 6.0 (Windows Vista). Once this is done, we set the timer interval to 1 second and enable it.

The control DispatcherTimer does not fire the Elapsed event as the usual Windows Forms Timer; in this case, we handle the Tick event:

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

When we receive the event, the first thing we do is turn off the timer. We do not want to shoot again until we're done. We call our method GetPowerCapabilities and check if your computer contains batteries, if so, call the method setValue which is responsible for passing the values to display on our controls, and finally re-enable the timer to run its normal refresh cycle.

The last method needed, setValue, just gets the necessary energy values and passes them for visualizing in our application:

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

And with that, if we compile our application (and are working on Windows 7, with a laptop or tablet), we should see all the information that you see at the beginning of the article.

Thanks for reading and happy coding!

History

  • 19 July 2010 - First edition.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here