Delphi: How to respond to WM_SettingChange/WM_WinI

2019-07-05 05:07发布

问题:

i need to know when my application recieves a WM_SETTINGCHANGE message (formerly known as WM_WININICHANGE).

Problem is that the message pump in TApplication sends it down a black hole (default handler) before i can get a chance to see it:

procedure TApplication.WndProc(var Message: TMessage);
...
begin
   Message.Result := 0;

   for I := 0 to FWindowHooks.Count - 1 do
      if TWindowHook(FWindowHooks[I]^)(Message) then Exit;

   CheckIniChange(Message);

   with Message do
      case Msg of
      WM_SETTINGCHANGE:
         begin
            Mouse.SettingChanged(wParam);
            Default;   <----------------------*poof* down the sink hole
         end;
      ...
      end;
      ...
end;

The procedure CheckIniChange() doesn't throw any event i can handle, neither does Mouse.SettingChanged().

And once the code path reaches Default, it is sent down the DefWindowProc drain hole, never to be seen again (since the first thing the WndProc does is set the Message.Result to zero.

i was hoping to assign a handler to a TApplicationEvents.OnMessage event:

procedure TdmGlobal.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
   case Msg.message of
   WM_SETTINGCHANGE:
      begin
         // Code
      end;
   end;
end;

But the OnMessage event is only thrown for messages that come through the message pump. Since the WM_SETTINGCHANGE message is "handled", it never sees the

PeekMessage
TranslateMessage
DispatchMessage

system.

How can i respond to the windows broadcast WM_SETTINGCHANGE?

回答1:

Edit2: For older versions the usual message intercept should work...

[...]
  private
    procedure WMSettingChange(var Message: TWMSettingChange); message WM_SETTINGCHANGE;
[...]
procedure TForm1.WMSettingChange(var Message: TWMSettingChange);
begin
  showMessage('SettingChange message intercept');
end;

Edit: Ooops! Did not see it was for D5. The following was in D2007+.

Use an OnSettingChange in your Application:

procedure TApplication.SettingChange(var Message: TWMSettingChange);
begin
  if Assigned(FOnSettingChange) then
    with Message do
      FOnSettingChange(Self, Flag, Section, Result);
end;

You can test with this code. Try and change the height or docking side of the TaskBar...

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnSettingChange := MySettingChange;
end;

procedure TForm1.MySettingChange(Sender: TObject; Flag: Integer;
  const Section: string; var Result: Integer);
begin
  showMessage('setting changed');
end;


回答2:

The answer was in my question, the dangerous, undocumented, use of HookMainWindow:

procedure TdmGlobal.DataModuleCreate(Sender: TObject);
begin
   ...
   Application.HookMainWindow(SettingChangeHook);
end;

procedure TdmGlobal.DataModuleDestroy(Sender: TObject);
begin
   Application.UnhookMainWindow(SettingChangeHook);
end;

function TdmGlobal.SettingChangeHook(var Message: TMessage): Boolean;
begin
   case Message.Msg of
   WM_SETTINGCHANGE:
      begin
         //Handler code
      end;
   end;

   Result := False; //continue processing
end;


回答3:

You handle it just like you would any other message. TApplication's default handler will route it correctly if you set up a message handler for it:

// interface
type
  TYourMainForm = class(TForm)
    // other stuff
  private
    procedure WMSettingChange(var Msg: TWMSettingChange); message WM_SETTINGCHANGE;
  end;

// implementation
procedure TYourMainForm.WMSettingChange(var Msg: TWMSettingChange); message WM_SETTINGCHANGE;
begin
  // Whatever handling here. TWMSettingChange is defined in Messages.pas
end;