Как найти положение максимального окна?

Мне нужно знать положение максимизированного окна.

В окне WPF есть свойства Top и Left, которые определяют расположение окна. Однако, если вы максимизируете окно, эти свойства сохраняют в нем значения нормального состояния.

Если вы используете одноэкранную настройку, максимизированное положение естественно (0,0). Однако, если у вас несколько экранов, это необязательно. Окно будет иметь только положение (0,0), если оно будет максимизировано на главном экране.

Итак... есть ли способ узнать положение максимизированного окна (желательно в тех же логических единицах, что и свойства Top и Left)?

Ответ 1

Здесь решение, которое я придумал, основывалось на предыдущем обсуждении здесь (спасибо!).

Это решение...

  • возвращает положение окна в текущем состоянии
  • обрабатывает все состояния окон (максимизируется, минимизируется, восстанавливается)
  • не зависит от Windows Forms (но вдохновлен им)
  • использует дескриптор окна для надежного определения правильного монитора

Основной метод GetAbsolutePosition реализуется здесь как метод расширения. Если у вас есть Window, называемый myWindow, назовите его следующим образом:

Point p = myWindow.GetAbsolutePosition();

Здесь полный код:

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

Ответ 2

Наконец-то я нашел решение для меня:

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

Ответ 3

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

Ответ 4

Универсальный однострочный ответ для всех мониторов и всех вариантов с высоким разрешением DPI:

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

Это, например, возвращает (-8, -8) для окна, которое максимизируется на широкоэкранном экране 1920 года, чей ActualWidth возвращает 1936.

Просто скажите другим ответам: НИКОГДА не смешивайте логические пиксели WPF с 96 точек на дюйм (используя double) с собственными реальными пикселями (используя int) - особенно, просто используя двойное преобразование в int!

Ответ 5

Я понимаю, что вы работаете в WPF, и этот ответ использует технологию Forms, но он должен работать без особых трудностей.

Вы можете получить коллекцию экранов через My.Settings.Screens.AllScreens. Оттуда вы можете получить доступ к разрешению, на котором в данный момент работает экран.

Так как окна WPF сохраняют значения Top/Left, которые у них были, когда они были максимальны, вы можете определить, на каком экране они находятся, выясняя, на каком экране находятся координаты Top/Left, а затем получить верхнюю/левую координату для этого экрана.

К сожалению, я нахожусь в дороге и не могу проверить это на данный момент. Если вы это сделаете, я бы с удовольствием посмотрел, что вы придумали.

Ответ 6

Это похоже на проблему с System.Windows.Window !!!

Развернутое окно дает ненадежные значения для Left, Width, ActualWidth, Top, Height и ActualHeight.

После максимизации окна оно часто может сохранять значения Left и Width из предварительно максимизированного окна.

Для других читателей - нет проблем, когда окно не развернуто.

Кроме того, я нахожу странным то, что значения, которые вы читаете, находятся в координатах WPF DPI, [то есть 1936x1096, от (-8, -8) до (1928, 1088)], но когда вы устанавливаете эти значения, вы должны использовать координаты пикселя на экране, [то есть 1920x1080, используя (0,0) и т.д.]

@tgr предоставил надежное частичное решение выше, которое я улучшил ниже:

  • Исправление intellisense для методов расширения путем перемещения вспомогательного класса в подкласс
  • создание метода GetAbsoluteRect() для обеспечения ширины/высоты и указания всех в одном вызове
  • рефакторинг общего кода

Вот решение С#:

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

Ответ 7

Я не нашел решения вашей проблемы, но если вам нужно расположить окно просто для создания нового, вы можете сделать следующее:

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