2
votes

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

2

2 Answers

2
votes

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

This version adds a WPF Button that when clicked displays the bounds of 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();
        System.Windows.Controls.Button wpfButton = new System.Windows.Controls.Button { Content = "Button" };
        wpfButton.Click += wpfButton_Click;

        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.Content = wpfButton;
        w.ShowDialog();
    }

    void wpfButton_Click(object sender, System.Windows.RoutedEventArgs e) {
        var wpfButton = (System.Windows.Controls.Button) sender;
        var wpfWindow = (System.Windows.Window) wpfButton.Parent;
        var helper = new WindowInteropHelper(wpfWindow);
        int GWL_HWNDPARENT = -8;
        IntPtr hwndMainForm = GetWindowLong(helper.Handle, GWL_HWNDPARENT);
        RECT r = new RECT();
        GetWindowRect(hwndMainForm, out r);
        SimpleWindow sw = new SimpleWindow { Handle = helper.Handle };
        MessageBox.Show(sw, "x:" + r.Left + " y:" + r.Top + " w:" + (r.Right - r.Left) + " h:" + (r.Bottom - r.Top), "Main Form Bounds");
    }

    private class SimpleWindow : System.Windows.Forms.IWin32Window {
        public IntPtr Handle { get; set; }
    }

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

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

    private static IntPtr GetWindowLongInternal(IntPtr hWnd, int nIndex) {
        if (IntPtr.Size == 4)
            return GetWindowLong(hWnd, (int) nIndex);

        return GetWindowLongPtr(hWnd, (int) nIndex);
    }
}