delphidelphi-10.1-berlin

PostMessage for all instances of a specific form (ClassName):


In a VCL Forms program, I have a Form that implements a method for handling windows messages and updating some controls on the Form, something like:

procedure OnMsgTest (var Msg: TMessage); message WM_CUSTOMTEST;

I use PostMessage with a custom message to this Form, using a code like this:

  h := FindWindow('TFrmTest', nil);    
  if IsWindow(h) then begin    
    PostMessage(h, WM_CUSTOMTEST, 0, 0);    
  end;

When the Form is instantiated several times, using the above code to send the message, only one Form instance updates the information on the screen. I would like all open and instantiated Forms to receive the message.

An important note: PostMessage can occur within the Form process itself, but also from another process. So, I believe a loop through the Forms would not work.

What would be the best approach to reach my goal?


Solution

  • You would have to enumerate all running top-level windows, posting the message to each matching window individually. You can use EnumWindows() and GetClassName() for that purpose, for example:

    const
      WM_CUSTOMTEST = ...;
    
    type
      TMyForm = class(TForm)
      private
        procedure BroadcastTest;
        procedure OnMsgTest(var Msg: TMessage); message WM_CUSTOMTEST;
      end;
    
    ...
    
    function BroadcastProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
    var
      ClsName: array[0..10] of Char;
    begin
      GetClassName(hwnd, ClsName, Length(ClsName));
      if StrComp(ClsName, 'TFrmTest') = 0 then
        PostMessage(hwnd, WM_CUSTOMTEST, 0, 0);
      Result := TRUE;
    end;
    
    procedure TMyForm.BroadcastTest;
    begin
      EnumWindows(@BroadcastProc, 0);
    end;
    
    procedure TMyForm.OnMsgTest(var Msg: TMessage);
    begin
      ...
    end;
    

    That being said, a more reliable and safer option would be to use RegisterWindowMessage() with PostMessage(HWND_BROADCAST). Let the OS do the enumeration for you. Only windows that are interested in the message will react to it, while other windows will ignore it. This way, the class name doesn't matter at all. However, since RegisterWindowMessage() is dynamic, you can't use a message handler, but you can have your Form override the virtual WndProc() method instead. For example:

    type
      TMyForm = class(TForm)
      protected
        procedure WndProc(var Msg: TMessage); override;
      private
        procedure BroadcastTest;
      end;
        
    ...
    
    var
      WM_CUSTOMTEST: UINT = 0;
        
    procedure TMyForm.BroadcastTest;
    begin
      if WM_CUSTOMTEST <> 0 then
        PostMessage(HWND_BROADCAST, WM_CUSTOMTEST, 0, 0);    
    end;
    
    procedure TMyForm.WndProc(var Msg: TMessage);
    begin
      if (Msg.Msg = WM_CUSTOMTEST) and (WM_CUSTOMTEST <> 0) then
      begin
        ...
      end else
        inherited;
    end;
    
    initialization
      WM_CUSTOMTEST := RegisterWindowMessage('SomeUniqueNameHere');