C # WPF & WinForms Interop - CenterOwner nicht wirksam

Nov 28 2020

Ich arbeite an einer Legacy-Anwendung, die WPF und WinForms für ihre Benutzeroberfläche verwendet. WPF macht die überwiegende Mehrheit aus, aber der Hauptdialog der Anwendung befindet sich immer noch in WinForms.

Bisher konnte ich sie gut zusammenarbeiten lassen (dank System.Windows.Forms.Integration.ElementHost), aber ich kann die WPF-Fenster nicht dazu bringen, sich auf das übergeordnete WinForms-Fenster zu konzentrieren.

Mein Code sieht folgendermaßen aus.

WPF-Steuerung (im ElementHost gehostet)

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

OwnershipHelper (entnommen aushttps://stackoverflow.com/a/36606974/13567181)

Diese Klasse stellt die Eltern-Kind-Beziehung her, sodass verschachtelte Dialoge auf demselben Bildschirm geöffnet und zusammen mit ihren Eltern minimiert werden.

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

Das Problem, mit dem ich konfrontiert bin, ist, dass bei der Ausführung von dialog.ShowDialog () das neu geöffnete Fenster überhaupt nicht um seinen Besitzer zentriert ist. Es befindet sich irgendwo auf dem Bildschirm, aber ich verstehe nicht ganz, wie es seinen Standort bestimmt.

Interessanterweise ist dieses neue Fenster perfekt um das übergeordnete SubWindow- Fenster zentriert , wenn ich den ButtonBase_OnClick- Code in der SubWindow- Klasse erneut wiederhole .

Aus meiner Sicht hat dies etwas mit dem zu tun Elemente Eltern von Subwindow .

Kann mir jemand raten, wie das SubWindow-Zentrum um das übergeordnete Element herum erstellt wird, ohne die Position manuell zu berechnen? (ähnlich wie dieshttps://stackoverflow.com/a/42401001/13567181)

EDIT: Ich habe das gerade auf MSDN gefunden - es ist irgendwie ähnlich, aber ich bin unsicher. https://social.msdn.microsoft.com/Forums/vstudio/en-US/05768951-73cf-4daf-b369-0905ca7e5222/centering-wpf-window-on-winforms-owner-window?forum=wpf

Viele Grüße Norbert

Antworten

2 Loathing Nov 28 2020 at 08:58

Verwenden Sie Application.Run(new MyForm2());und klicken Sie dann auf die Schaltfläche, um eine zu erstellen WPF Window, die sich auf das Hauptformular konzentriert.

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

Diese Version fügt eine hinzu WPF Button, die beim Klicken die Grenzen des Hauptformulars anzeigt.

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