tags:

views:

294

answers:

2

I recently inspect a GUI with Microsoft's Spy++ and noticed a strange structure; it looked like this (warning, ASCII art ahead):

 |
 + 002004D6 "MyRootWindow1" FooClassName
 |   |
 |   + 001F052C "MyChildWindow" ClassOfChildWindow
 |
 \ 001D0A8C "MyRootWindow2" SomeOtherClassName

There are two root windows, 002004D6 and 001D0A8c, the former one of which has one child window, 001F052C.

Now, this would be all good and find if it wasn't for one thing: calling GetParent (or watching the 'Parent Window' or 'Owner Window' fields in Spy++) on the child window (001F052C) yields 001D0A8C.

Read: "MyChildWindow" is a child of "MyRootWindow1", but "MyRootWindow1" is not the parent of "MyChildWindow". Instead, the parent of "MyChildWindow" is "MyRootWindow2" - but, to make this complete, enumerating the children of "MyRootWindow2" does not yield "MyChildWindow".

This is a perfectly static GUI applications, so there are no race conditions here or anything.

Does anybody know how this can happen? Does anybody know how I can work around this? Until now, I used GetParent and EnumChildWindows to get the parent (or children) for a given HWND, and I assumed that this relationship is symmetrical. Is there maybe something else I should be using?

EDIT: Here's the code for a small C++ program which demonstrates the problem:

const HINSTANCE thisModule = ::GetModuleHandle( NULL );
HWND oldParent = ::CreateWindow( TEXT("STATIC"),
                                 TEXT("Old parent"),
                                 WS_VISIBLE | WS_BORDER,
                                 0, 0, 850, 500,
                                 NULL,
                                 NULL,
                                 thisModule,
                                 NULL );
HWND child = ::CreateWindow( TEXT("STATIC"),
                             TEXT("This is a sample dialog"),
                             WS_OVERLAPPED | WS_POPUP | WS_VISIBLE | WS_BORDER,
                             100, 100, 300, 300,
                             oldParent,
                             NULL,
                             thisModule,
                             NULL );
HWND newParent = ::CreateWindow( TEXT("STATIC"),
                                 TEXT("Fake main window"),
                                 WS_VISIBLE | WS_BORDER,
                                 0, 0, 850, 500,
                                 NULL,
                                 NULL,
                                 thisModule,
                                 NULL );
::SetParent( child, newParent );

Note how the 'child' object has WS_POPUP and WS_OVERLAPPED set, but not WS_CHILD.

+3  A: 

Well, that doesn't make a lot of sense of course. It smells like the child window is re-parenting itself. It is a common technique in .NET Windows Forms, it has a "Parking window" where child controls can find a temporary home when their container window needs to be recreated because a window style changed. This isn't a very visible effect, it is also temporary.

Another, remote, possibility is SetParent(). It has appcompat behavior to support old Windows 3.x programs. It is explained pretty well in the SDK docs for it, in a nutshell a window can be parented but not set its WS_CHILD style flag. Adobe Acrobat Reader is a classic example of a program that does this. What effect that will have on EnumChildWindows is unclear to me.

Last but not least: don't forget that Spy++ gives a static view of the windows. Pressing F5 to update the window list can be important to track changes.

Not great explanations. Try to find out if it matters which toplevel window is active, I suspect it does.

Hans Passant
Thanks for your insightful answer! I also considered a reparenting issue, but I would have thought that this would properly update all links (so a reparenting action decreases the children of the old parent by one and increases the children of the new parent by one). I wouldn't have thought that it's possible to get into such a state. The idea with compatibility behaviour is very interesting, I'll see whether I can find anything about that!
Frerich Raabe
+2  A: 

The documentation for GetParent explains: "Note that, despite its name, this function can return an owner window instead of a parent window. "

As you are not creating a child window i'm guessing you hit this case.

You should be able to call GetAncestor passing GA_PARENT as the documentation says: "Retrieves the parent window. This does not include the owner, as it does with the GetParent function."

See http://stackoverflow.com/questions/507848/win32-window-owner-vs-window-parent

morechilli
This doesn't work in my case; GetAncestor(GA_PARENT) still returns the wrong parent (`oldParent` in the example code). The problem is that the SetParent() call didn't update the reference.
Frerich Raabe
I'm confused as to what you are trying to achieve - SetParent will only work if you set the WS_CHILD flag on the child window before calling it.
morechilli
Yes, WS_CHILD is missing. I pointed that out in my last sentence. :-) The shown code is unfortunately not under my control. It's part of the code for a GUI application which I want to introspect. What I'm trying to achieve is a being able to get the parent (or child) of any given HWND even if the window hierarchy is broken like shown in the code above. This probably means that I have to emulate GetParent using Enum(Child)Windows, or the other way round.
Frerich Raabe