Tuesday, February 28, 2017

Center WPF window in Win32 window

I needed to add a new WPF window, while developing Visual Studio Package. And it would be good for it to be centered in parent.
I've found an old msdn blog post that was supposed help in this. However it is not ideal and has some missing things. Let's fix that.



The main part of the post looks like this:

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 == nullreturn;

    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;
};

There are two problem for me: getting owner window coordinates and also I had problems executing a final couple of lines:

    cw.Left = ownerWPFPosition.X + (ownerWPFSize.X – cw.Width) / 2;    cw.Top = ownerWPFPosition.Y + (ownerWPFSize.Y – cw.Height) / 2;

The problem here is that Width and Height of my window are set to NaN and this makes Left and Top being NaN too and that's result in default position.

Step #1. Get Win32 window coordinates can be solved by importing Win32 API method and structure:
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

And then calling it like:

                RECT rect = new RECT();
                GetWindowRect(hwnd, ref rect);

Step #2. Replace window Width and Height with ActualWidth and ActualHeight. And final version of modified code looks like this:

        public static void CenterInOwner(System.Windows.Window window, IntPtr ownerHwnd)
        {
            window.SourceInitialized += delegate
            {
                RECT rect = new RECT();
                GetWindowRect(ownerHwnd, ref rect);

                WindowInteropHelper helper = new WindowInteropHelper(window);
                HwndSource source = HwndSource.FromHwnd(helper.Handle);

                if (source == null)
                    return;

                Matrix matrix = source.CompositionTarget.TransformFromDevice;

                Point ownerWPFSize = matrix.Transform(new Point(rect.Right - rect.Left, rect.Bottom - rect.Top));
                Point ownerWPFPosition = matrix.Transform(new Point(rect.Left, rect.Top));

                window.WindowStartupLocation = WindowStartupLocation.Manual;
                window.Left = ownerWPFPosition.X + (ownerWPFSize.X - window.ActualWidth) / 2;
                window.Top = ownerWPFPosition.Y + (ownerWPFSize.Y - window.ActualHeight) / 2;
            };
        }

It should be called before calling window.ShowDialog

P.S. Don't forget about imports and usings.

No comments:

Post a Comment