Issue
It is frustrating when windows open on another display other than the one you clicked on or display partially off screen, requiring you to move and resize the window.
With WPF (Windows Presentation Foundation), this issue occurs when you configure the window to resize to its contents. The final values for ActualHeight and ActualWidth are set after the window has been loaded, displayed and its contents rendered. Therefore, centering the window on the Loaded event results in the top left corner being centered near the middle of the screen with the bottom right portion of the window possibly off screen.
Solution
System.Windows.Forms.Screen provides a one line solution to this issue unlike the solutions based on importing user32.dll, which requires declaring several structures to use with GetWindowPlacement().
To re-center a window when it resizes to accommodate content or when a user resizes it, add an event handler for the window SizeChanged event. The SizeChanged event occurs when either the ActualHeight or the ActualWidth properties change value.
realWindow.SizeChanged += (sender, e) => ReCenterWindowEventHandler(sender, e, realWindow);
Next put the event handler in the base class of your application presentation code.
/// Recenter this window to parent window.
public void ReCenterWindowEventHandler(object sender, EventArgs e, System.Windows.Window window)
{
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (!double.IsNaN(window.ActualHeight)
&& !double.IsNaN(window.ActualWidth)
&& !double.IsNaN(window.Owner.ActualHeight)
&& !double.IsNaN(window.Owner.ActualWidth))
{
WindowPlacement.ConstrainAndCenterWindowToScreen(window: window);
}
}));
}
The class below, WindowPlacement, provides methods to recenter a window to the screen it is displayed in or the screen of another window. See the code comments detailing what each method does.
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
namespace Presentation
{
static internal class WindowPlacement
{
/// <summary>
/// Resize window to fit screen, then center window to screen.
/// </summary>
/// <param name="window">Window to be centered.</param>
public static void ConstrainAndCenterWindowToScreen(Window window)
{
// Gets the window handle for a Windows Presentation Foundation (WPF) window that is used to create this WindowInteropHelper.
var windowInteropHelperHandle = new WindowInteropHelper(window).Handle;
// Gets the horizontal resolution of this Graphics. 96DPI is the standard pixel density.
float scale = 96.0F / System.Drawing.Graphics.FromHwnd(windowInteropHelperHandle).DpiX;
// The width and height values of Screen are not scaled.
var screen = Screen.FromHandle(windowInteropHelperHandle);
var screenWorkingAreaHeightScaled = screen.WorkingArea.Height * scale;
var screenWorkingAreaWidthScaled = screen.WorkingArea.Width * scale;
// If actual height or width of window is greater than the screen then set height or width.
if ((window.ActualHeight > screenWorkingAreaHeightScaled) || (window.WindowState == WindowState.Maximized))
{
window.Height = screenWorkingAreaHeightScaled;
window.MaxHeight = screenWorkingAreaHeightScaled;
}
if ((window.ActualWidth > screenWorkingAreaWidthScaled) || (window.WindowState == WindowState.Maximized))
{
window.Width = screenWorkingAreaWidthScaled;
window.MaxWidth = screenWorkingAreaWidthScaled;
}
// Recenter window within the screen.
window.Top = (screen.WorkingArea.Top * scale) + (screenWorkingAreaHeightScaled - window.Height) / 2;
window.Left = (screen.WorkingArea.Left * scale) + (screenWorkingAreaWidthScaled - window.Width) / 2;
}
/// <summary>
/// Resize and center window to owning window.
/// </summary>
/// <param name="window">Window to be centered.</param>
public static void ConstrainAndCenterWindowToWindowOwner(Window window)
{
// If actual height or width of window is greater than the screen then set height or width.
if ((window.ActualHeight > window.Owner.Height) || (window.WindowState == WindowState.Maximized))
{
window.Height = window.Owner.Height;
window.MaxHeight = window.Owner.Height;
}
if ((window.ActualWidth > window.Owner.Width) || (window.WindowState == WindowState.Maximized))
{
window.Width = window.Owner.Width;
window.MaxWidth = window.Owner.Width;
}
window.Top = window.Owner.Top + (window.Owner.Height - window.Height) / 2;
window.Left = window.Owner.Left + (window.Owner.Width - window.Width) / 2;
}
}
}
To understand how these methods work, we need to understand the display coordinate system in Windows. The primary display’s top left corner is the center of the X, Y axis with positive values increasing to the right and down. Displays to the left of the primary display have negative X values and displays to the right have positive X values. Displays above the primary display have negative Y values and displays below the primary display have positive Y values. Here is an example of two displays with the primary below the secondary.
The System.Windows.Forms.Screen.AllScreens property returns an array containing all displays. Each array item contains the coordinates of the entire screen and the working area of the screen. Notice the values for the left, top, right and bottom properties for both the Bounds and WorkingArea.
Conclusion
Searching which screen a window opened in or resizing a window to fit the monitor screen is frustrating. Programmatically centering windows within the screen the user is working in and resizing them to fit the screen provides a much better user experience.