I'm trying to set childForm
as the child of the main Excel window using the SetParent API through PInvoke:
Form childForm = new MyForm();
IntPtr excelHandle = (IntPtr) excelApplication.Hwnd;
SetParent(childForm.Handle, excelHandle);
childForm.StartPosition = FormStartPosition.Manual;
childForm.Left = 0;
childForm.Top = 0;
As you can see above, my intention is also to position the child in the top left corner of Excel window. However, for some reason the childForm
always ends up at some weird location.
What is it that I am doing wrong?
While all answers here suggest perfectly logical approaches, none of them worked for me. Then I tried MoveWindow. For some reason I don't understand, it did the job.
Here's the code:
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
...
Form childForm = new MyForm();
IntPtr excelHandle = (IntPtr) excelApplication.Hwnd;
SetParent(childForm.Handle, excelHandle);
MoveWindow(childForm.Handle, 0, 0, childForm.Width, childForm.Height, true);
When using SetParent
on a form that is currently a child of the desktop (in other words, one without a parent
set), you must set the WS_CHILD
style and remove the WS_POPUP
style. (See the Remarks section of the MSDN entry.) Windows requires that all owned windows have the WS_CHILD
style set. This could also be causing the left and top properties to report/set the wrong values because the form doesn't know who it's daddy is. You can fix this by calling SetWindowLong
after SetParent
, but before you try to set the location:
//Remove WS_POPUP style and add WS_CHILD style
const UInt32 WS_POPUP = 0x80000000;
const UInt32 WS_CHILD = 0x40000000;
int style = GetWindowLong(this.Handle, GWL_STYLE);
style = (style & ~(WS_POPUP)) | WS_CHILD;
SetWindowLong(this.Handle, GWL_STYLE, style);
It depends on your ShowDialog call I believe. If you call ShowDialog without the parent paremeter, the parent is reset.
You could create a wrapper class that implements IWin32Window and returns the HWND to excel. Then you could pass that to the ShowDialog call of childForm.
You could also query the position of the excel application using GetWindowPos and then set the childForm accordingly.
Try a few things to diagnose the problem:
- Put a breakpoint after setting Left
and Top, do Left and Top read zero?
- Call SetParent last.
- Make a method that sets Left and Top
again, and BeginInvoke the method.
- Make sure your child window is really
the child. To do this call
ShowDialog, and try to click the
parent window. Make sure windows
prevents focus to the parent window.
Assuming you know how to get the hwnds of the windows you want to set z-order of, you can use this pInvoke:
public stati class WindowsApi
{
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, uint uFlags);
}
public class WindowZOrderPositioner
{
public void SetZOrder(IntPtr targetHwnd, IntPtr insertAfter)
{
IntPtr nextHwnd = IntPtr.Zero;
WindowsAPI.SetWindowPos(targetHwnd, insertAfter, 0, 0, 0, 0, SetWindowPosFlags.NoMove | SetWindowPosFlags.NoSize | SetWindowPosFlags.NoActivate);
}