Search code examples
delphidelphi-xe6

How to use RegisterPowerSettingNotification


I want to be notified when my computer power source changes.

So first I 've created a simple Delphi application and listening for WM_POWERBROADCAST at the main form.

WM_POWERBROADCAST

type
  TForm38 = class(TForm)
  public
    procedure WM_POWERBROADCAST(var Msg: TMessage); message WM_POWERBROADCAST;
  end;

implementation

procedure TForm38.WM_POWERBROADCAST(var Msg: TMessage);
begin
  Caption := Msg.LParam.ToString;
end;

Then I got my notifications, but Msg.LParam is allways 0 (zero)

Then I've tried to call RegisterPowerSettingNotification and found an example in this old SO Question, but I still have the same problem: Msg.LParam is allways 0 (zero)

RegisterPowerSettingNotification

type
  TForm38 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FHPOWERNOTIFY: HPOWERNOTIFY;
  public
    { Public declarations }
    procedure WM_POWERBROADCAST(var Msg: TMessage); message WM_POWERBROADCAST;
  end;

implementation

const
  GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';

procedure TForm38.FormCreate(Sender: TObject);
begin
  FHPOWERNOTIFY := RegisterPowerSettingNotification(Handle, GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
end;

procedure TForm38.FormDestroy(Sender: TObject);
begin
  UnregisterPowerSettingNotification(FHPOWERNOTIFY);
end;

procedure TForm38.WM_POWERBROADCAST(var Msg: TMessage);
begin
  Caption := Msg.LParam.ToString;
end;

The application run on Windows 10.

What am I doing wrong?

THE RESULT

Using the code from the answer to this question, I've ended up writing this class:

unit PowerWatcherU;

interface

uses
  Winapi.Windows, System.Classes, System.SyncObjs, Winapi.Messages;

{$M+}

type
  TPowerSource = (PoAc = 0, PoDc = 1, PoHot = 2);
  TPowerSourceChanged = procedure(const PowerSource: TPowerSource) of object;

  TPowerWatcher = class(TComponent)
  private
    FMyHWND: HWND;
    FHPOWERNOTIFY: HPOWERNOTIFY;
    FOnPowerSourceChanged: TPowerSourceChanged;
    procedure DoPowerSourceChanged(const Value: TPowerSource);
    procedure WndHandler(var Msg: TMessage);
    procedure SetOnPowerSourceChanged(const Value: TPowerSourceChanged);
  published
    property OnPowerSourceChanged: TPowerSourceChanged read FOnPowerSourceChanged write SetOnPowerSourceChanged;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

const
  GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';

implementation

uses
  System.SysUtils;

{ TPowerWatcher }

constructor TPowerWatcher.Create;
begin
  inherited;
  FMyHWND := AllocateHWND(WndHandler);
  FHPOWERNOTIFY := RegisterPowerSettingNotification(FMyHWND, GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
end;

destructor TPowerWatcher.Destroy;
begin
  DeallocateHWND(FMyHWND);
  UnregisterPowerSettingNotification(FHPOWERNOTIFY);
  inherited;
end;

procedure TPowerWatcher.DoPowerSourceChanged(const Value: TPowerSource);
begin
  if Assigned(FOnPowerSourceChanged) then
    FOnPowerSourceChanged(Value);
end;

procedure TPowerWatcher.SetOnPowerSourceChanged(const Value: TPowerSourceChanged);
begin
  FOnPowerSourceChanged := Value;
end;

procedure TPowerWatcher.WndHandler(var Msg: TMessage);
begin
  if (Msg.Msg = WM_POWERBROADCAST) and (Msg.WParam = PBT_POWERSETTINGCHANGE) then
  begin
    if PPowerBroadcastSetting(Msg.LParam)^.PowerSetting = GUID_ACDC_POWER_SOURCE then
      DoPowerSourceChanged(TPowerSource(PPowerBroadcastSetting(Msg.LParam)^.Data[0]));
  end
  else
    Msg.Result := DefWindowProc(FMyHWND, Msg.Msg, Msg.WParam, Msg.LParam);
end;

end.

Solution

  • It is possible that you are suffering from window re-creation. Your code as posted works fine for me but this may not be the case in Win10. With that aside, the only other oddity is that you are duplicating an identifier by naming a method WM_POWERBROADCAST, although this should not cause the code to break. Working example using a dedicated HWND :

    unit Unit1;
    
    interface
    
    uses
      Windows, SysUtils, Classes, Forms, StdCtrls, Vcl.Controls, Vcl.ExtCtrls,
      Messages;
    
    type
    
      TForm1 = class(TForm)
        Button1: TButton;
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        private
          FMyHWND : HWND;
          FHPowerNotify: HPOWERNOTIFY;
        public
          procedure WndHandler(var Msg: TMessage);
     end;
    
    var
      Form1: TForm1;
    
    implementation
    {$R *.dfm}
    
    const
      GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      FMyHWND := AllocateHWND(WndHandler);
      FHPowerNotify := RegisterPowerSettingNotification(FMyHWND,
                                                        GUID_ACDC_POWER_SOURCE, 
                                                        DEVICE_NOTIFY_WINDOW_HANDLE);
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      UnregisterPowerSettingNotification(FHPowerNotify);
      DeallocateHWND(FMyHWND);
    end;
    
    procedure TForm1.WndHandler(var Msg: TMessage);
    begin
      if (Msg.Msg = WM_POWERBROADCAST) and
         (Msg.WParam = PBT_POWERSETTINGCHANGE) then
      begin
        if PPowerBroadcastSetting(Msg.LParam)^.PowerSetting = GUID_ACDC_POWER_SOURCE then
          case cardinal(PPowerBroadcastSetting(Msg.LParam)^.Data[0]) of
            0: Caption := 'AC Power';
            1: Caption := 'DC Power';
            2: Caption := 'HOT - UPS, etc';
          end;
      end else
        msg.Result := DefWindowProc(FMyHWND, Msg.Msg, Msg.WParam, Msg.LParam);
    end;
    
    end.