November 27, 2007
[Initial code - pre-baked]

What's the difference?

MSDN says:
Form.ShowDialog ()Shows the form as a modal dialog box with the currently active window set as its owner.
Form.ShowDialog(IWin32Window)Shows the form as a modal dialog box with the specified owner.

Why isn't the TopMost form the currently active window?

Fire up Reflector.

The guts of System.Windows.Forms.Form.ShowDialog() are simple:

public DialogResult ShowDialog()
return this.ShowDialog(null);

Pertinent code:

public DialogResult ShowDialog(IWin32Window owner);
IntPtr activeWindow = UnsafeNativeMethods.GetActiveWindow();
IntPtr ptr3 = (owner == null) ? activeWindow : Control.GetSafeHandle(owner);
UnsafeNativeMethods.SetWindowLong(new HandleRef(this, base.Handle), -8,
new HandleRef(owner, ptr3));

Sets a new extended window style. For more information, see CreateWindowEx.
Sets a new window style.

Sets a new address for the window procedure.

Windows NT/2000/XP: You cannot change this attribute if the window
does not belong to the same process as the calling thread.
Sets a new application instance handle.
Sets a new identifier of the window.
Sets the user data associated with the window. This data is intended
for use by the application that created the window. Its value is
initially zero.

The following values are also available when the hWnd parameter identifies
a dialog box.

Sets the new address of the dialog box procedure.
Sets the return value of a message processed in the dialog box
Sets new extra information that is private to the application, such as
handles or pointers.

-8 magic number? According to WinUser.h in the Platform SDK, -8 is the GWL_HWNDPARENT constant.

- MSDN docs had told us the ShowDialog methods set the owner of the opening form, not the parent.
- MSDN docs on SetWindowLong warn us away from using this constant:
You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window. Instead, use the SetParent function.

Why the is the .NET Framework doing something the docs say shouldn't be done?

Google Groups search on GWL_HWNDPARENT yields this explanation:
(In)famous misleading statement. Almost as misleading as the choice of
GWL_HWNDPARENT as the name. It has nothing to do with a window's
parent. It really changes the Owner...
A more accurate version might be..
“SetWindowLong with the GWL_HWNDPARENT will not change the parent of a
child window. Instead, use the SetParent function. GWL_HWNDPARENT
should have been called GWL_HWNDOWNER, but nobody noticed it until
after a bazillion copies of the SDK had gone out. This is what happens
when the the dev team lives on M&Ms and CocaCola for to long. Too bad.
Live with it.”

- Calling ShowDialog() should setup the current active window (which should be the calling form) as the owner.
- Calling ShowDialog(this) should setup the calling form (which should be the active form) as the owner.

- The call inside ShowDialog(IWin32Window) to GetActiveWindow isn't working.
- The call to SetWindowLong isn't working.
- Something else...

Spy++ comparisons:

In both cases, the form has the same styles and ex-styles:



Parent and Owner handles
ShowDialog()0calling form
ShowDialog(this)calling formcalling form

- SetWindowLong call is working properly
- GetActiveWindow call is also working
- Something else: parent setting.

We know where the owner relationship is setup in the ShowDialog method. Where is the parent relationship setup?

A search for SetParent in Reflector yields nothing too obvious (actually, I believe I spent a bit of time chasing this trail before coming up short. I thought I'd skip over that bit here). More research leads to CreateWindowEx, which takes a parent handle parameter.

What's the connection between ShowDialog and CreateWindowEx?

Reflector shows:

- CreateWindowEx
+- System.Windows.Forms.UnsafeNativeMethods.CreateWindowEx
+- System.Windows.Forms.NativeWindow.CreateHandle(CreateParams)
+- System.Windows.Forms.Control.CreateHandle()

NativeWindow.CreateHandle(CreateParams) passes in the Parent property of the CreateParams instance, which it receives from Control.CreateHandle(). Control.CreateHandle() gets the CreateParams instance from the CreateParams property of itself (Control):

CreateParams createParams = this.CreateParams;

Internally, properties have getter and setter methods, with get_ and set_ prefixed to the property name. Looking inside Control.get_CreateParams:

createParams.Parent = (this.parent == null) ? IntPtr.Zero :

Find all the places the parent field is set:

Dead end.

Break out MDbg, and put a breakpoint on System.Windows.Forms.Control.get_CreateParams.

Form.get_CreateParams overrides:

IWin32Window window = (IWin32Window) base.Properties.GetObject(PropDialogOwner);
if (window != null)
createParams.Parent = Control.GetSafeHandle(window);

Analyzer in Reflector shows not many uses of PropDialogOwner.

public DialogResult ShowDialog(IWin32Window owner);
IntPtr activeWindow = UnsafeNativeMethods.GetActiveWindow();
IntPtr ptr3 = (owner == null) ? activeWindow : Control.GetSafeHandle(owner);
base.Properties.SetObject(PropDialogOwner, owner);
UnsafeNativeMethods.SetWindowLong(new HandleRef(this, base.Handle), -8,
new HandleRef(owner, ptr3));

The fix. Add the following code somewhere in RegularForm:

using ...
using System.Runtime.InteropServices;

public class RegularForm:Form
protected override CreateParams CreateParams
CreateParams cp = base.CreateParams;
cp.Parent = (IntPtr)GetActiveWindow();
return cp;

[DllImport("USER32", CharSet=CharSet.Auto)]
extern static int GetActiveWindow();

Why does the CustomForm not work, even when calling ShowDialog(this)? Back to MDbg.

Same fix.

tags: ComputersAndTechnology TheCaseOfTheInconsistentParent
comments powered by Disqus