Centering WPF Windows with WPF and Non-WPF Owner Windows
This post demonstrates how to manually center a window with respect to both a WPF and non-WPF owner window.
Centering a Window with a WPF Owner Window
To center a window over another window in WPF, you need to do two things. First, you need to set the WindowStartupLocation property of the Window you want centered to WindowStartupLocation.CenterOwner:
<Window
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.CenteredWindow"
Title="Centered Window"
WindowStartupLocation = "CenterOwner"
Width="250" Height="250">
...
</Window>
using System.Windows; // Window
namespace SDKSample
{
public partial class CenteredWindow : Window
{
public CenteredWindow()
{
InitializeComponent();
}
}
}
Second, when you open the window to be centered, you *must* set its Owner property with the Window that opens it:
<Window
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.OwnerWindow"
Title="Owner Window"
Width="300" Height="300">
...
< ButtonClick = "button_Click">
Show Centered Window
</ Button >
...
</Window>
using System.Windows; // Window, RoutedEventArgs
namespace SDKSample
{
public partial class OwnerWindow : Window
{
public OwnerWindow()
{
InitializeComponent();
}
void button_Click(object sender, RoutedEventArgs e)
{
// Open window and set this window as its owner.
// This causes the window to be centered with
// respect to this window
CenteredWindow cw = new CenteredWindow();
cw.Owner = this;
cw.Show();
}
}
}
When the button in this example is clicked, the owned window appears centered with respect to the owner window, as shown in the following figure:
Centering a Window with a Non-WPF Owner Window
WPF does allow you to set a non-WPF window as the owner of a WPF window. For example, a Visual Studio 2005 (VS05) add-in that shows a WPF window whose owner is the main VS05 IDE window. The following shows a generalized version of code that sets a WPF window with a non-WPF owner, using WindowInteropHelper:
using System.Windows; // Window
using System.Windows.Interop; // WindowInteropHelper
...
// Instantiate the owned WPF window
CenteredWindow cw = new CenteredWindow();
// Get the handle to the non-WPF owner window
IntPtr ownerWindowHandle = ...;
// Set the owned WPF window’s Owner property with
// the non-WPF owner window
WindowInteropHelper helper = new WindowInteropHelper(cw);
helper.Owner = (IntPtr)_applicationObject.MainWindow.HWnd;
// Show the owned WPF window
cw.Show();
When a WPF window has a non-WPF owner, WPF honors the owner window/owned window relationship eg when a non-WPF owner window is minimized, the owned WPF window follows suit. However, if the owned WPF window has its WindowStartupLocation property set to WindowStartupLocation.CenterOwner, WPF does not center the owned WPF window over the non-WPF owner window. Instead, you’ll need to set the WindowStartupLocation of the owned window to WindowStartupLocation.Manual and manually calculate its Top and Left properties with values that make it appear centered:
using System.Windows; // Window, WindowStartupLocation
using System.Windows.Interop; // WindowInteropHelper
...
// Instantiate the owned WPF window
CenteredWindow cw = new CenteredWindow();
// Get the handle to the non-WPF owner window
IntPtr ownerWindowHandle = ...; // Get hWnd for non-WPF window
// Set the owned WPF window’s owner with the non-WPF owner window
WindowInteropHelper helper = new WindowInteropHelper(cw);
helper.Owner = ownerWindowHandle;
// Manually calculate Top/Left to appear centered
int nonWPFOwnerLeft = ...; // Get non-WPF owner’s Left
int nonWPFOwnerWidth = ...; // Get non-WPF owner’s Width
int nonWPFOwnerTop = ...; // Get non-WPF owner’s Top
int nonWPFOwnerHeight = ...; // Get non-WPF owner’s Height
cw.WindowStartupLocation = WindowStartupLocation.Manual;
cw.Left = nonWPFOwnerLeft + (nonWPFOwnerWidth - cw.Width) / 2;
cw.Top = nonWPFOwnerTop + (nonWPFOwnerHeight - cw.Height) / 2;
// Show the owned WPF window
cw.Show();
There is one issue with this code; WPF supports device-independence, which means that, irrespective of DPI; this code will center a WPF window with respect to a WPF owner window in both low and high DPI. However, this code may not center a WPF window with respect to a non-WPF owner window in high DPI. In this case, you need to perform a little extra work to convert the non-WPF owner window’s location and size into device-independent versions that the WPF owned window can calculate against. This work is facilitated by HwndSource:
using System.Windows; // Window, WindowStartupLocation, Point
using System.Windows.Interop; // WindowInteropHelper, HwndSource
using System.Windows.Media; // Matrix
...
// Instantiate the owned WPF window
CenteredWindow cw = new CenteredWindow();
// Get the handle to the non-WPF owner window
IntPtr ownerWindowHandle = ...; // Get hWnd for non-WPF window
// Set the owned WPF window’s owner with the non-WPF owner window
WindowInteropHelper helper = new WindowInteropHelper(cw);
helper.Owner = ownerWindowHandle;
// Center window
// Note - Need to use HwndSource to get handle to WPF owned window,
// and the handle only exists when SourceInitialized has been
// raised
cw.SourceInitialized += delegate
{
// Get WPF size and location for non-WPF owner window
int nonWPFOwnerLeft = ...; // Get non-WPF owner’s Left
int nonWPFOwnerWidth = ...; // Get non-WPF owner’s Width
int nonWPFOwnerTop = ...; // Get non-WPF owner’s Top
int nonWPFOwnerHeight = ...; // Get non-WPF owner’s Height
// Get transform matrix to transform non-WPF owner window
// size and location units into device-independent WPF
// size and location units
HwndSource source = HwndSource.FromHwnd(helper.Handle);
if (source == null) return;
Matrix matrix = source.CompositionTarget.TransformFromDevice;
Point ownerWPFSize = matrix.Transform(
new Point(nonWPFOwnerWidth, nonWPFOwnerHeight));
Point ownerWPFPosition = matrix.Transform(
new Point(nonWPFOwnerLeft, nonWPFOwnerTop));
// Center WPF window
cw.WindowStartupLocation = WindowStartupLocation.Manual;
cw.Left = ownerWPFPosition.X + (ownerWPFSize.X - cw.Width) / 2;
cw.Top = ownerWPFPosition.Y + (ownerWPFSize.Y - cw.Height) / 2;
};
// Show WPF owned window
cw.Show();
This code basically converts the non-device-independent size and position of the non-WPF owner window, and converts them to device-independent values. This allows you to calculate the Top and Left values that will cause the owned WPF window to be appeared centered. This requires a little help from HwndSource, which is used to do the co-ordinate system conversion (or transforms). HwndSource relies on the WPF window having a hWnd, which doesn’t happen until the SourceInitialized event is raised. Hence the anonymous delegate code.
This solution is generalized, but does work in both normal and high DPI.
Comments
Anonymous
May 14, 2007
在前一个Post当中,指出了在WPF的WindowInteropHelper类中的一个BUG:通过WindowInteropHelper的Owner属性不能实现把WPF窗口的Owner属性设置为一个非WPF窗口的句柄。在我的Post帖出后不到一天,在WPF SDK的Blog上,就针对这个BUG给出了一个非常完美的解决方案。既然不同通过设置WindowStartupLocation.CenterOwner来改变窗口的位置。那么我们就用WindowStartupLocation.Manual来手动计算设置窗口的位置Anonymous
June 22, 2011
I don't know when this changed, but at least in .NET 4.0 WPF will honor the WindowStartupLocation="CenterOwner" setting even with a non-WPF owner window.