PresentNotes

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));
...
}








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

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.
GWL_HINSTANCE
Sets a new application instance handle.
GWL_ID
Sets a new identifier of the window.
GWL_USERDATA
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.

DWL_DLGPROC
Sets the new address of the dialog box procedure.
DWL_MSGRESULT
Sets the return value of a message processed in the dialog box
procedure.
DWL_USER
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.”









Summary:
- 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.

Options:
- 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:

WS_CAPTION
WS_VISIBLE
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
WS_SYSMENU
WS_THICKFRAME
WS_OVERLAPPED
WS_MINIMIZEBOX
WS_MAXIMIZEBOX

WS_EX_LEFT
WS_EX_LTRREADING
WS_EX_RIGHTSCROLLBAR
WS_EX_WINDOWEDGE
WS_EX_CONTROLPARENT
WS_EX_APPWINDOW


Parent and Owner handles
CallParentOwner
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 :
this.parent.InternalHandle;


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
{
get
{
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