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.