4

In a C++ program (embarcadero XE2, vcl) I would like to send window-messages from parent to all child windows. For this, I registered a windowMessage, send the message with PostMessage(handle,msg,wparam,lparam) in a loop for all handles and receive it on each dialog with WndProc(TMessage& Message).

My problem is to keep track of the open window handles. Since most dialogs are opened via Show(), multiple of them can run at the same time.

So far I used a std::vector<HWND> to store the Window-Handles. However, this would require me to keep track of which handle is still valid at a time. I could solve this by adding a onClose Handler to the dialogs and call a procedure in the main thread with the dialog's handle as a parameter, so it can be removed from the vector...

Is there a nicer solution, like a self-updating list as in Application.OpenForms (.NET)? Or maybe a better way to notify child-dialog of an event from the main dialog?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Julian
  • 493
  • 4
  • 22
  • 1
    [In Windows, dialog boxes are not *children*, they are *owned windows*.](https://blogs.msdn.microsoft.com/oldnewthing/20100315-00/?p=14613) – andlabs Jun 12 '17 at 12:44
  • This seems like a horrible use of a registered window message, which is best used when applications from different vendors are trying to communicate - probably via broadcast messages. An application sending its own windows messages would be far better served using the WM_USER or WM_APP defined ranges. – Chris Becke Jun 12 '17 at 12:46
  • @andlabs Thanks for the comment! I wouldn't have been able to find out why Cody's solution wouldn't work if you didn't mention the difference between children and owned dialogs. ^^ – Julian Jun 13 '17 at 08:34

2 Answers2

4

A window already tracks its children internally, so you just need to tap into that. If you want to send a message to all of a window's child windows, then you just need to recursively iterate through all of that window's children, sending the message to each one.

The starting point is the GetTopWindow function, which returns the child window at the top of the Z order. Then, you iterate through the child windows by calling the GetNextWindow function.

MFC actually includes a method that does this, called SendMessageToDescendants. You can write the equivalent yourself, and replace SendMessage with PostMessage, if you'd prefer those semantics.

void PostMessageToDescendants(HWND   hwndParent,
                              UINT   uMsg,
                              WPARAM wParam,
                              LPARAM lParam,
                              BOOL   bRecursive)
{
   // Walk through all child windows of the specified parent.
   for (HWND hwndChild = GetTopWindow(hwndParent);
        hwndChild     != NULL;
        hwndChild      = GetNextWindow(hwndChild, GW_HWNDNEXT))
   {
      // Post the message to this window.
      PostMessage(hwndChild, uMsg, wParam, lParam);

      // Then, if necessary, call this function recursively to post the message
      // to all levels of descendant windows.
      if (bRecursive && (GetTopWindow(hwndChild) != NULL))
      {
         PostMessageToDescendants(hwndChild, uMsg, wParam, lParam, bRecursive);
      }
   }
}

The arguments are the same as the PostMessage function, except for the last one: bRecursive. This parameter means just what its name suggests. If TRUE, the search of child windows will be recursive, so that the message will be posted to all descendants of the parent window (its children, its children's children, etc.). If FALSE, the message will be posted only to its immediate children.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Thanks for the quick and explained answer! `GetNextWindow(hwndChild, GW_HWNDNEXT))` didn't seem to compile, but `GetWindow(hwndChild, GW_HWNDNEXT)` seems to do the intended... I didn't mention before I was using owlnext for the main application MDI window (for historic reasons) and vcl for the child forms. The children have three lines in their constructor to get opened: `tf = new TForm(Owner->Handle); tf->ParentWindow = Owner->Handle; this->PopupParent = tf; ` With your code, `WndProc`doesn't receive the message anymore... maybe it's stuck at `TForm *tf`? – Julian Jun 12 '17 at 12:51
  • This answer won't actually send messages to owned dialogs. However, I'm not sure how that would be fixed... would we need to enumerate all top-level windows and filter out the ones that aren't owned by the window in question? A recursive search this way would take much longer because it would have to re-read the entire list every time :| And what about the distinction between topmost and top-level windows that MSDN makes? – andlabs Jun 12 '17 at 12:51
  • The question was about child windows, @andlabs, nothing about *owned* windows. – Cody Gray - on strike Jun 12 '17 at 13:10
  • It makes no sense that `GetNextWindow(hwndChild, GW_HWNDNEXT))` wouldn't compile. Something is wrong with your environment. I've used this code hundreds of times. – Cody Gray - on strike Jun 12 '17 at 13:11
  • @CodyGray Well, since I use owlnext I have a `window.h` where it is defined as `inline HWND TWindow::GetNextWindow(uint flag) const { PRECONDITION(GetHandle()); return ::GetWindow(GetHandle(), flag); }` so as a handle, the calling object's handle is taken... About not receiving any WindowMessages at the child-vcl-dialogs: is the problem that they are actually not childs but owned forms? Is there an adaption of your solution to owned windows ? – Julian Jun 12 '17 at 13:47
  • @CodyGray yes, but the term used was "child dialogs", so I'm not sure which word takes precedence. – andlabs Jun 12 '17 at 14:41
0

Cody Gray has given the right solution to the question I asked.

However, as shown in the comments, I asked the wrong question.

My dialogs were vcl TForms which were opened by Owl TWindow, which means I use the ParentWindow property of the dialogs to get a modal appearance:

__fastcall TMyDialog::MyDialog(owl::TWindow* Owner) :TForm(HWND(NULL)){
    tf = new TForm(Owner->Handle); //TForm*, Mainwindow calls this dialog
    tf->ParentWindow = Owner->Handle; // workaround: owl calls vcl
    this->PopupParent = tf; // workaround: owl calls vcl
}

So, the result is likely a mix of children and owned dialogs.

What worked for me to get Window Messages to all dialogs that were opened from the main window was this:

struct checkMSG{
    HWND handle; UINT msg; WPARAM wparam; LPARAM lparam;
};
checkMSG msgCheck;

BOOL CALLBACK enumWindowsProc(__in  HWND hWnd,__in  LPARAM lParam) {
     HWND owner= ::GetParent(hWnd);
     if(owner==msgCheck.handle)
        ::PostMessage(hWnd, msgCheck.msg, msgCheck.wparam, msgCheck.lparam);
     return TRUE;
}

void SendToAllWindows(HWND handle, UINT msg, WPARAM wparam, LPARAM lparam){
    msgCheck.handle=::GetParent(handle);
    msgCheck.msg=msg;
    msgCheck.wparam= wparam;
    msgCheck.lparam= lparam;
    BOOL enumeratingWindowsSucceeded = ::EnumWindows( enumWindowsProc, NULL );
}
  • EnumWindows iterates about all windows in all applications.
  • To be sure to only get my own application's window's handles, I used a variable msgCheck to store the toplevel handle and the message I want to send.
  • Now I use GetParent which retrieves the Owner or the Parent or returns NULL if neither is found.
  • In the callback function the stored toplevel handle is compared to the found window's toplevel handle and if they match, the window message is send to the found window's handle
Julian
  • 493
  • 4
  • 22