Warm tip: This article is reproduced from serverfault.com, please click

CenterOwner not effective

发布于 2020-11-27 23:33:26

I'm working on a legacy application that uses WPF and WinForms for its UI. WPF makes up for the vast majority but the application main dialog is still in WinForms.

So far I've been able to have them work together nicely (thanks to System.Windows.Forms.Integration.ElementHost) but I can't get the WPF windows to center on their WinForms parent.

My code looks the following.

WPF Control (hosted in the ElementHost)

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    var dialog = new SubWindow();
    WindowOwnershipHelper.SetOwner(dialog, this);
    dialog.ShowDialog();
}

OwnershipHelper (taken from https://stackoverflow.com/a/36606974/13567181)

This class establishes the 'parent-child' relationship so that nested dialogs open on the same screen and minimize together with their parent...

public static class WindowOwnershipHelper
{
    public static void SetOwner(Window window, Visual parent)
    {
        var source = (HwndSource) PresentationSource.FromVisual(parent);

        if (source == null)
        {
            throw new InvalidOperationException("Could not determine parent from visual.");
        }

        new WindowInteropHelper(window).Owner = source.Handle;
    }
}

The problem that I'm facing is that when dialog.ShowDialog() executes, the newly opened window is not at all centered around its owner. It is somewhere on the screen but I don't quite understand how it determines its location.

Interestingly, if I repeat the ButtonBase_OnClick code inside the SubWindow class again, this new window is perfectly centered around its SubWindow parent.

From my point of view, this has something to do with the ElementHost parent of SubWindow.

Can someone advise me on how the get the SubWindow center around its parent without manually calculating its position? (similar to this https://stackoverflow.com/a/42401001/13567181)

EDIT: I just found this on MSDN - its somehow similar but I'm uncertain. https://social.msdn.microsoft.com/Forums/vstudio/en-US/05768951-73cf-4daf-b369-0905ca7e5222/centering-wpf-window-on-winforms-owner-window?forum=wpf

Best regards Norbert

Questioner
Norbert Hüthmayr
Viewed
0
Loathing 2020-11-28 09:58:18

Use Application.Run(new MyForm2()); and then click the button to create a WPF Window that centers on the main form.

public class MyForm2 : Form {

    public MyForm2() {
        this.Size = new Size(600,600);
        this.StartPosition = FormStartPosition.CenterScreen;
        Button btn = new Button { Text = "New Window", AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink };
        btn.Click += btn_Click;
        Controls.Add(btn);
    }

    void btn_Click(object sender, EventArgs e) {
        var w = new System.Windows.Window();
        w.SourceInitialized += w_SourceInitialized;
        w.Width = 400.0; // number of actual pixels might be different
        w.Height = 400.0; // depending on DPI. My laptop is 120 dpi, so 400.0 -> 400 * 120 / 96 = 500 pixels.
        w.Title = "WPF Window";
        w.ShowDialog();
    }

    void w_SourceInitialized(object sender, EventArgs e) {
        var w = (System.Windows.Window) sender;
        WindowInteropHelper helper = new WindowInteropHelper(w);
        //w.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner; does nothing
        int GWL_HWNDPARENT = -8;
        SetWindowLongInternal(helper.Handle, GWL_HWNDPARENT, this.Handle);

        Rectangle r = this.Bounds;
        RECT r2 = new RECT();
        GetWindowRect(helper.Handle, out r2);
        int w2 = r2.Right - r2.Left;
        int h2 = r2.Bottom - r2.Top;

        int x2 = r.X + (r.Width - w2) / 2;
        int y2 = r.Y + (r.Height - h2) / 2;

        uint SWP_NOSIZE        = 0x0001;
        uint SWP_NOZORDER      = 0x0004;
        uint SWP_NOREDRAW      = 0x0008;
        uint SWP_NOACTIVATE    = 0x0010;
        uint SWP_NOCOPYBITS    = 0x0100;
        uint SWP_NOOWNERZORDER = 0x0200;
        uint flags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER;

        SetWindowPos(helper.Handle, IntPtr.Zero, x2, y2, 0, 0, flags);
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [DllImport("user32.dll", SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int w, int h, uint uFlags);

    [DllImport("user32.dll", SetLastError=true)]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    [DllImport("user32.dll", SetLastError=true)]
    private static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    private static int SetWindowLongInternal(IntPtr hWnd, int nIndex, IntPtr dwNewLong) {
        if (IntPtr.Size == 4)
            return SetWindowLong(hWnd, nIndex, dwNewLong);

        return SetWindowLongPtr(hWnd, nIndex, dwNewLong);
    }
}