14

I need to know the position of a window that is maximized.

WPF Window has Top and Left properties that specifies the window's location. However, if you maximize the window these properties keep the values of the window in it's normal state.

If you´re running on a single-screen setup, the maximized position is naturally (0,0). However, if you have multiple screens that is not necessarily true. The window will only have position (0,0) if you have it maximized on the main screen.

So... is there any way to find out the position of a maximized window (preferably in the same logical units as the Top and Left properties)?

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
haagel
  • 2,646
  • 10
  • 36
  • 53

7 Answers7

11

Here's the solution I came up with based on previous discussion here (thanks!).

This solution...

  • returns the position of a window in its current state
  • handles all window states (maximized, minimized, restored)
  • does not depend on Windows Forms (but is inspired by it)
  • uses the window handle to reliably determine the correct monitor

The main method GetAbsolutePosition is implemented here as an extension method. If you have a Window called myWindow, call it like this:

Point p = myWindow.GetAbsolutePosition();

Here's the complete code:

using System;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;

static class OSInterop
{
    [DllImport("user32.dll")]
    public static extern int GetSystemMetrics(int smIndex);
    public const int SM_CMONITORS = 80;

    [DllImport("user32.dll")]
    public static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX info);

    [DllImport("user32.dll")]
    public static extern IntPtr MonitorFromWindow(HandleRef handle, int flags);

    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
        public int width { get { return right - left; } }
        public int height { get { return bottom - top; } }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
    public class MONITORINFOEX
    {
        public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
        public RECT rcMonitor = new RECT();
        public RECT rcWork = new RECT();
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public char[] szDevice = new char[32];
        public int dwFlags;
    }
}

static class WPFExtensionMethods
{
    public static Point GetAbsolutePosition(this Window w)
    {
        if (w.WindowState != WindowState.Maximized)
            return new Point(w.Left, w.Top);

        Int32Rect r;
        bool multimonSupported = OSInterop.GetSystemMetrics(OSInterop.SM_CMONITORS) != 0;
        if (!multimonSupported)
        {
            OSInterop.RECT rc = new OSInterop.RECT();
            OSInterop.SystemParametersInfo(48, 0, ref rc, 0);
            r = new Int32Rect(rc.left, rc.top, rc.width, rc.height);
        }
        else
        {
            WindowInteropHelper helper = new WindowInteropHelper(w);
            IntPtr hmonitor = OSInterop.MonitorFromWindow(new HandleRef((object)null, helper.EnsureHandle()), 2);
            OSInterop.MONITORINFOEX info = new OSInterop.MONITORINFOEX();
            OSInterop.GetMonitorInfo(new HandleRef((object)null, hmonitor), info);
            r = new Int32Rect(info.rcWork.left, info.rcWork.top, info.rcWork.width, info.rcWork.height);
        }
        return new Point(r.X, r.Y);
    }
}
tgr42
  • 790
  • 8
  • 11
  • Very helpful, thanks. I'd also recommend adding another method to your WPFExtensionMethods, to GetAbsoluteRect, returning a new Rect(r.X, r.Y, r.Width, r.Height), since you still can't rely on window.ActualWidth or window.Left when the screen is unmaximized. – stuzor Jun 29 '19 at 09:43
10
public static System.Drawing.Rectangle GetWindowRectangle(this Window w)
{
    if (w.WindowState == WindowState.Maximized) {
        var handle = new System.Windows.Interop.WindowInteropHelper(w).Handle;
        var screen = System.Windows.Forms.Screen.FromHandle(handle);
        return screen.WorkingArea;
    }
    else {
        return new System.Drawing.Rectangle(
            (int)w.Left, (int)w.Top, 
            (int)w.ActualWidth, (int)w.ActualHeight);
    }
}
Mike Blandford
  • 3,952
  • 4
  • 29
  • 32
  • 2
    Goldilocks! The first solution was compact but wrong, second was probably right, but not compact. This one is just right :) For some reason the 3 are voted in reverse order of their actual value. – ILIA BROUDNO Feb 24 '16 at 20:07
  • YES!!! Verify simple and works perfectly with multi-screens! Thanks! – Chklang Feb 16 '21 at 16:40
8

I finally found a solution working for me:

private System.Drawing.Rectangle getWindowRectangle()
{
    System.Drawing.Rectangle windowRectangle;

    if (this.WindowState == System.Windows.WindowState.Maximized)
    {
        /* Here is the magic:
         * Use Winforms code to find the Available space on the
         * screen that contained the window 
         * just before it was maximized
         * (Left, Top have their values from Normal WindowState)
         */
        windowRectangle = System.Windows.Forms.Screen.GetWorkingArea(
            new System.Drawing.Point((int)this.Left, (int)this.Top));
    }
    else
    {
        windowRectangle = new System.Drawing.Rectangle(
            (int)this.Left, (int)this.Top,
            (int)this.ActualWidth, (int)this.ActualHeight);
    }

    return windowRectangle;
}
eFloh
  • 2,098
  • 20
  • 24
  • 2
    I think if you change the GetWorkingArea call to: windowRectangle = System.Windows.Forms.Screen.GetWorkingArea(new System.Drawing.Point((int)window.Left + (int)(window.ActualWidth / 2), (int)window.Top + (int)(window.ActualHeight / 2))); that will fix it. You need to get the centre of the window, not the topleft, because that's the point that WPF uses to determine which window to maximize to, if the window is across more than one monitor. – Matt Sep 29 '11 at 09:01
  • 1
    good idea, but does this actually help? this branch of the if-clause is only used when the WindowState is Maximized, so ActualWith and -Height will be about the screen size, not the (restored) windowsize – eFloh Sep 29 '11 at 14:04
  • Yeah, I get what you're saying. can you get the restored window size from RestoreBounds and then use that instead of ActualWidth/ActualHeight perhaps? – Matt Sep 29 '11 at 14:54
  • 1
    This fails if the application starts as maximized on one of multiple monitors. – Zack Peterson Aug 01 '12 at 22:33
  • Unfortunately this solution only gives the correct answer SOME of the time What is wrong is the assumption that in Maximized state the window's Left and Top properties are what they were just before it was maximized. After some checking it turned out to be false. – ILIA BROUDNO Feb 24 '16 at 19:47
6

Universal one line answer working for all monitors and all high DPI variants:

Point leftTop = this.PointToScreen(new Point(0, 0));

This for example returns (-8, -8) for a window which is maximized on a 1920 wide screen whose ActualWidth returns 1936.

Just a rant to the other answers: NEVER mix up logical 96 dpi WPF pixels (using double) with native real pixels (using int) - especially by just casting double to int!

springy76
  • 3,706
  • 2
  • 24
  • 46
  • It obviously doesn't work on multiple monitors setups. – Kilazur Sep 15 '16 at 08:23
  • @Kilazur why obviously? I initially tested this on a triple head on all 3 monitors. The task bar is vertical and not on the primary screen which alone freaks out 50% of all software. – springy76 Sep 15 '16 at 14:51
  • 1
    The point (0,0) is inside the client area of the window, and does not return the actual upper/left corner of the window frame. If you try to use these coordinates to place something else, it comes out wrong. – Jeff B Nov 25 '16 at 16:38
  • Annoying that such a simple thing is thwarting everything – Epirocks Feb 22 '17 at 15:44
  • You can use the answer in [this question](https://stackoverflow.com/questions/6032032/how-do-i-compute-the-non-client-window-size-in-wpf) to adjust for non-client area. This is the only answer that gave me consistent results, and is simplest too. – GazTheDestroyer Sep 20 '17 at 09:41
0

I realize that you are working in WPF, and this answer makes use of Forms technology, but it should work without much difficulty.

You can get a collection of the screens through My.Settings.Screens.AllScreens. From there you can access the resolution that the screen is currently working on.

Since WPF windows retain the Top/Left values that they had when they were maximized, then you can determine which screen they are on by figuring out which screen that Top/Left coordinates refer to, and then get the top/left coordinate for that screen.

Unfortunately, I am on the road, and can't test this at the moment. If you do implement, I would love to see what you come up with.

CodeWarrior
  • 7,388
  • 7
  • 51
  • 78
0

This seems to be a problem with System.Windows.Window!!!

Maximized window give unreliable values for Left, Width, ActualWidth, Top, Height and ActualHeight.

After maximizing a window, it can often keep the Left and Width values from the pre-maximized window.

For others reading - there is no problem when the window is un-maximized.

Also, what I find odd is that the values you read are in WPF DPI coordinates, [i.e. 1936x1096, from (-8, -8) to (1928, 1088)], but when you set these values you have to use screen pixel coordinates, [i.e. 1920x1080, using (0,0) etc...]

@tgr provided a reliable partial solution above, which I've improved below:

  • Fixing intellisense for extension methods by moving helper class to sub class
  • creating GetAbsoluteRect() method to provide Width/Height and point all in one call
  • refactoring common code

Here's the C# solution:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

public static partial class Extensions
{
    static class OSInterop
    {
        [DllImport("user32.dll")]
        public static extern int GetSystemMetrics(int smIndex);
        public const int SM_CMONITORS = 80;

        [DllImport("user32.dll")]
        public static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX info);

        [DllImport("user32.dll")]
        public static extern IntPtr MonitorFromWindow(HandleRef handle, int flags);

        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
            public int width { get { return right - left; } }
            public int height { get { return bottom - top; } }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
        public class MONITORINFOEX
        {
            public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
            public RECT rcMonitor = new RECT();
            public RECT rcWork = new RECT();
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public char[] szDevice = new char[32];
            public int dwFlags;
        }
    }

    static Int32Rect _getOsInteropRect(Window w)
    {
        bool multimonSupported = OSInterop.GetSystemMetrics(OSInterop.SM_CMONITORS) != 0;
        if (!multimonSupported)
        {
            OSInterop.RECT rc = new OSInterop.RECT();
            OSInterop.SystemParametersInfo(48, 0, ref rc, 0);
            return new Int32Rect(rc.left, rc.top, rc.width, rc.height);
        }

        WindowInteropHelper helper = new WindowInteropHelper(w);
        IntPtr hmonitor = OSInterop.MonitorFromWindow(new HandleRef((object)null, helper.EnsureHandle()), 2);
        OSInterop.MONITORINFOEX info = new OSInterop.MONITORINFOEX();
        OSInterop.GetMonitorInfo(new HandleRef((object)null, hmonitor), info);
        return new Int32Rect(info.rcWork.left, info.rcWork.top, info.rcWork.width, info.rcWork.height);
    }

    public static Rect GetAbsoluteRect(this Window w)
    {
        if (w.WindowState != WindowState.Maximized)
            return new Rect(w.Left, w.Top, w.ActualWidth, w.ActualHeight);

        var r = _getOsInteropRect(w);
        return new Rect(r.X, r.Y, r.Width, r.Height);
    }

    public static Point GetAbsolutePosition(this Window w)
    {
        if (w.WindowState != WindowState.Maximized)
            return new Point(w.Left, w.Top);

        var r = _getOsInteropRect(w);
        return new Point(r.X, r.Y);
    }
}
stuzor
  • 2,275
  • 1
  • 31
  • 45
0

I haven't found a solution to your problem, but if you need to position the window just to create a new one you can do the following:

...
Window windowNew = new Window();
ConfigureWindow(this, windowNew);
Window.Show();
...

static public void ConfigureWindow(Window windowOld, Window windowNew)
    {
        windowNew.Height = windowOld.ActualHeight;
        windowNew.Width = windowOld.ActualWidth;

        if (windowOld.WindowState == WindowState.Maximized)
        {
            windowNew.WindowState = WindowState.Maximized;
        }
        else
        {
            windowNew.Top = windowOld.Top;
            windowNew.Left = windowOld.Left;
        }
    }