Open chucker opened 1 month ago
I've written/amended extension methods to help myself in the meantime. Sharing this for others who run into the same problem. This will work in net472
, net7.0-windows
and newer.
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using WinForms = System.Windows.Forms;
namespace EL.Client.WpfUtils.WinFormsInterop
{
public static class WindowOwnerExtensions
{
public static void SetOwner(this Window window, WinForms.Control winFormsControl)
{
_ = new WindowInteropHelper(window)
{
Owner = winFormsControl.Handle
};
}
/// <summary>
/// <para>
/// Set a WPF window's owner to a Win32/WinForms owner, centers to, then
/// opens it. Do not set <c>WindowStartupLocation</c> separately.
/// </para>
///
/// <para>
/// This is necessary because WPF's code
/// https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Window.cs,1872024dfc3ed928
/// gets the wrong metrics when a Win32 owner is maximized: it treats
/// the owner as though it <em>weren't</em> maximized. Thus, with
/// <see cref="WindowStartupLocation.CenterOwner"/> and a Win32 owner
/// that's maximized, the center would be wrong.
/// </para>
/// </summary>
/// <param name="window">The WPF window</param>
/// <param name="winFormsControl">The owning Win32 control</param>
public static void ShowWithConcentricOwner(this Window window, WinForms.Control winFormsControl)
{
SetOwner(window, winFormsControl);
bool isMaximized = IsWinFormsOwnerMaximized(winFormsControl);
if (!isMaximized)
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
else
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
window.Show();
}
/// <summary>
/// <para>
/// Set a WPF window's owner to a Win32/WinForms owner, centers to, then
/// opens it. Do not set <c>WindowStartupLocation</c> separately.
/// </para>
///
/// <para>
/// Returns only when the window is closed.
/// </para>
///
/// <para>
/// This is necessary because WPF's code
/// https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Window.cs,1872024dfc3ed928
/// gets the wrong metrics when a Win32 owner is maximized: it treats
/// the owner as though it <em>weren't</em> maximized. Thus, with
/// <see cref="WindowStartupLocation.CenterOwner"/> and a Win32 owner
/// that's maximized, the center would be wrong.
/// </para>
/// </summary>
/// <param name="window">The WPF window</param>
/// <param name="winFormsControl">The owning Win32 control</param>
public static bool? ShowDialogWithConcentricOwner(this Window window, WinForms.Control winFormsControl)
{
SetOwner(window, winFormsControl);
bool isMaximized = IsWinFormsOwnerMaximized(winFormsControl);
if (!isMaximized)
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
else
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
return window.ShowDialog();
}
private static bool IsWinFormsOwnerMaximized(WinForms.Control winFormsControl)
{
bool isMaximized;
Windows.Win32.UI.WindowsAndMessaging.WINDOWPLACEMENT windowPlacement = new();
windowPlacement.length = (uint)Marshal.SizeOf(windowPlacement);
Windows.Win32.Foundation.HWND windowHandle = (Windows.Win32.Foundation.HWND)winFormsControl.Handle;
if (!Windows.Win32.PInvoke.GetWindowPlacement(windowHandle, ref windowPlacement))
isMaximized = false; // fall back to WPF code
else
isMaximized = windowPlacement.showCmd == Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_MAXIMIZE;
return isMaximized;
}
}
}
(GetWindowPlacement
and related symbols are source-generated with CsWin32.)
Description
I see that a code path exists for using
WindowStartupLocation.CenterOwner
with a Win32 owner, namely theelse
branch after https://source.dot.net/#PresentationFramework/System/Windows/Window.cs,3659.And indeed, this works, as long as that owner isn't maximized. If it is maximized, the window position isn't what I would expect.
Reproduction Steps
Take a WPF app template.
Enable WinForms interop in the
csproj
:Modify
App.xaml
to not have aStartupUri
:Modify
MainWindow.xaml
to be a little smaller:Finally, add a constructor to
App
that creates a WinForms form and usesMainWindow
:Expected behavior
The WPF window should show up as concentric to the form.
Actual behavior
If we comment out
form.WindowState = System.Windows.Forms.FormWindowState.Maximized;
and thus the form shows as a regular window, this does work.But if the form is maximized, the WPF window instead is concentric to where the form would be if it weren't maximized. I don't believe this behavior makes sense.
Regression?
None.
Reproducible in
net47
,net7.0-windows
,net8.0-windows
.Known Workarounds
I would like one, especially one that works with .NET Framework 4.7.2.
I'm guessing I need to call
SetupInitialState
, perhaps by callingCreateSourceWindow(duringShow: false)
, then fixing the location, then callShow()
?Impact
We have a legacy primarily WinForms app that we're adding WPF stuff to bit by bit, including by adding WPF-based dialogs — which, preferably, would center correctly.
Configuration
net472
,net7.0-windows
,net8.0-windows
net472
), ARM64I don't think this is specific to the above configuration.
Other information
It appears
CalculateWindowLocation
does consider the case of "what if the owning WPF window is maximized", but not "what if the owning Win32 form is maximized".