C # WPF i WinForms Interop - CenterOwner nieskuteczne

Nov 28 2020

Pracuję nad starszą aplikacją, która używa WPF i WinForms dla swojego interfejsu użytkownika. WPF stanowi zdecydowaną większość, ale główne okno dialogowe aplikacji jest nadal w WinForms.

Jak dotąd udało mi się sprawić, że dobrze ze sobą współpracują (dzięki System.Windows.Forms.Integration.ElementHost), ale nie mogę wyśrodkować okien WPF na ich rodzicu WinForms.

Mój kod wygląda następująco.

Formant WPF (hostowany w ElementHost)

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

OwnershipHelper (pobrane zhttps://stackoverflow.com/a/36606974/13567181)

Ta klasa ustanawia relację `` rodzic-dziecko '', dzięki czemu zagnieżdżone okna dialogowe otwierają się na tym samym ekranie i minimalizują razem z rodzicem ...

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

Problem, z którym się spotykam, polega na tym, że po uruchomieniu okna dialog.ShowDialog () nowo otwarte okno nie jest w ogóle wyśrodkowane wokół swojego właściciela. Jest gdzieś na ekranie, ale nie do końca rozumiem, jak określa jego lokalizację.

Co ciekawe, jeśli ponownie powtórzę kod ButtonBase_OnClick wewnątrz klasy SubWindow , to nowe okno jest idealnie wyśrodkowane wokół jego elementu nadrzędnego SubWindow .

Z mojego punktu widzenia ma to coś wspólnego z elementem macierzystym ElementHost SubWindow .

Czy ktoś może mi doradzić, jak ustawić SubWindow wyśrodkować wokół swojego rodzica bez ręcznego obliczania jego pozycji? (podobnie do tegohttps://stackoverflow.com/a/42401001/13567181)

EDYCJA: Właśnie znalazłem to w MSDN - jest w jakiś sposób podobne, ale nie jestem pewien. https://social.msdn.microsoft.com/Forums/vstudio/en-US/05768951-73cf-4daf-b369-0905ca7e5222/centering-wpf-window-on-winforms-owner-window?forum=wpf

Pozdrawiam Norbert

Odpowiedzi

2 Loathing Nov 28 2020 at 08:58

Użyj, Application.Run(new MyForm2());a następnie kliknij przycisk, aby utworzyć WPF Windowwyśrodkowany na głównym formularzu.

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 Loathing Nov 29 2020 at 05:23

Ta wersja dodaje, WPF Buttonże po kliknięciu wyświetla granice głównego formularza.

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