Problem positioning window when using SetParent()

2019-07-02 05:23发布

问题:

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?

回答1:

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


回答2:

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


回答3:

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.



回答4:

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.


回答5:

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