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=http://schemas.microsoft.com/winfx/2006/xaml/presentation


  xmlns:x=http://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=http://schemas.microsoft.com/winfx/2006/xaml/presentation


  xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml


  x:Class=SDKSample.OwnerWindow


  Title=Owner Window


  Width=300 Height=300>


   


    <Button Click=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 (2)

  1. Yiling Lai says:

    在前一个Post当中,指出了在WPF的WindowInteropHelper类中的一个BUG:通过WindowInteropHelper的Owner属性不能实现把WPF窗口的Owner属性设置为一个非WPF窗口的句柄。在我的Post帖出后不到一天,在WPF SDK的Blog上,就针对这个BUG给出了一个非常完美的解决方案。既然不同通过设置WindowStartupLocation.CenterOwner来改变窗口的位置。那么我们就用WindowStartupLocation.Manual来手动计算设置窗口的位置

  2. oddsignals says:

    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.