c# - Использование SetWindowPos с несколькими мониторами



windows user32 (1)

Система отображает расположение и VirtualScreen

В системе Windows основной экран (перспектива программирования) - это устройство отображения, положение верхнего левого угла которого установлено на Point(0,0) .

Это означает, что дисплеи, расположенные слева от основного экрана, будут иметь отрицательные координаты X (координата Y может быть отрицательной, если дисплей находится в книжном макете).
Дисплеи справа будут иметь положительные координаты X (координата Y может быть отрицательной, если дисплей находится в книжном макете).

Отображается слева от основного экрана :
Другими словами, дисплеи с отрицательным происхождением Point.X
Point.X координат Point.X является суммой всех предыдущих Screens[].Width , вычтенных из Point.X координаты Point.X экрана.

Отображается справа от основного экрана :
Другими словами, дисплеи, которые имеют положительное происхождение Point.X
Point.X координат Point.X является суммой всех предыдущих Screens[].Width , включая первичный , добавляется к координатной Point.X первичного экрана.

Важное примечание :
Если приложение не DPIAware, все эти меры могут быть скомпрометированы виртуализацией и автоматическим масштабированием DPI, выполненным Системой. Все меры будут унифицированы до значения по умолчанию 96 точек на дюйм: приложение будет получать масштабированные значения. Сюда также входят значения, полученные из функций Api не-DpiAware Win32. Увидеть:

Разработка приложений для настольных ПК с высоким разрешением на Windows

Включите поддержку всех целевых Систем в файле app.manifest , раскомментировав необходимые разделы.

Добавьте / раскомментируйте разделы DpiAware и DpiAwareness в файле app.manifest .
Режим PerMonitorV2 Dpi Awareness можно установить в файле app.config (доступно в Windows 10 Creators Edition).

Пример:
Рассмотрим систему с 3 мониторами:

PrimaryScreen             (\\.\DISPLAY1):  Width: (1920 x 1080)
Secondary Display (Right) (\\.\DISPLAY2):  Width: (1360 x 768)
Secondary Display (Left)  (\\.\DISPLAY3):  Width: (1680 x 1050)

PrimaryScreen: 
     Bounds: (0, 0, 1920, 1080)      Left: 0      Right: 1920  Top: 0  Bottom: 1080
Secondary Display (Right): 
     Bounds: (1360, 0, 1360, 768)    Left: 1360   Right: 2720  Top: 0  Bottom: 768
Secondary Display (Left): 
     Bounds: (-1680, 0, 1680, 1050)  Left: -1680  Right: 0     Top: 0  Bottom: 1050



Если мы изменим с помощью системного апплета ссылку на основной экран, установив для него значение \\.\DISPLAY3 , координаты будут изменены соответствующим образом:


Виртуальный экран

Виртуальный экран - это виртуальный дисплей, размеры которого представлены:
Начало координат: начальная координата самого левого Screen
Ширина : сумма всех ширин Screens .
Высота : высота самого высокого Screen

Эти меры сообщаются SystemInformation.VirtualScreen
Size SystemInformation.PrimaryMonitorSize экрана сообщается SystemInformation.PrimaryMonitorSize
Все текущие измерения и позиции экранов также можно получить с помощью Screen.AllScreens и проверки каждого свойства \\.\DISPLAY[N] .

Используя предыдущий пример в качестве ссылки, в первом расположении границы VirtualScreen :

Bounds: (-1680, 0, 3280, 1080)  Left: -1680  Right: 3280   Top: 0  Bottom: 1080

Во втором расположении границы VirtualScreen :

Bounds: (0, 0, 4960, 1080)  Left: 0  Right: 4960   Top: 0  Bottom: 1080


Положение окна внутри области отображения :

Класс Screen предлагает несколько методов, которые можно использовать для определения того, на каком экране в данный момент отображается конкретное окно:

Screen.FromControl([Control reference])
Возвращает объект Screen который содержит самый большой раздел указанной ссылки Control .

Screen.FromHandle([Window Handle])
Возвращает объект Screen который содержит самый большой раздел Window \ Control, на который ссылается Handle

Screen.FromPoint([Point])
Возвращает объект Screen который содержит конкретную Point

Screen.FromRectangle([Rectangle])
Возвращает объект Screen который содержит самый большой раздел указанного Rectangle

Screen.GetBounds() (перегружен)
Возвращает структуру Rectangle которая ссылается на границы экрана, которые содержат:
- конкретный Point
- самый большой участок указанного Rectangle
- Control ссылка

Чтобы определить \\.\DISPLAY[N] в котором отображается текущая форма, позвоните (например):

Screen.FromHandle(this);

Чтобы определить, на каком экране отображается вторичная форма:
(Использование примера дисплеев в примере)

form2 = new Form2();
form2.Location = new Point(-1400, 100);
form2.Show();
Rectangle screenSize = Screen.GetBounds(form2);
Screen screen = Screen.FromHandle(form2.Handle);

screenSize будет = до \\.\DISPLAY3 Bounds.
screen будет объектом Screen представляющим свойства \\.\DISPLAY3 .

screen объект также сообщит \\.\DISPLAY[N] имя Screen в котором отображается form2 .

Получите hMonitor объекта Screen :

Справочный источник .NET показывает, что hMonitor вызывая [Screen].GetHashCode();

IntPtr monitorHwnd = new IntPtr([Screen].GetHashCode());

Или используя те же родные функции Win32:

MonitorFromWindow , MonitorFromPoint и MonitorFromRect

[Flags]
internal enum MONITOR_DEFAULTTO
{
    NULL = 0x00000000,
    PRIMARY = 0x00000001,
    NEAREST = 0x00000002,
}

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR_DEFAULTTO dwFlags);

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromPoint([In] POINT pt, MONITOR_DEFAULTTO dwFlags);

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromRect([In] ref RECT lprc, MONITOR_DEFAULTTO dwFlags);

Получить дескриптор контекста устройства экрана :
Универсальный метод для получения hDC любого доступного дисплея.

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

Свойство Screen.DeviceName можно использовать в качестве параметра CreateDC функции GDI + CreateDC . Он вернет hDC дисплея, который Graphics.FromHdc может использовать для создания допустимого объекта Graphics, который позволит рисовать на определенном экране.

Здесь, при условии, что доступно по крайней мере два дисплея:

[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

[DllImport("gdi32.dll", SetLastError = true, EntryPoint = "DeleteDC")]
internal static extern bool DeleteDC([In] IntPtr hdc);  

public static IntPtr CreateDCFromDeviceName(string deviceName)
{
    return CreateDC(deviceName, null, null, IntPtr.Zero);
}


Screen[] screens = Screen.AllScreens;
IntPtr screenDC1 = CreateDCFromDeviceName(screens[0].DeviceName);
IntPtr screenDC2 = CreateDCFromDeviceName(screens[1].DeviceName);
using (Graphics g1 = Graphics.FromHdc(screenDC1))
using (Graphics g2 = Graphics.FromHdc(screenDC2))
using (Pen pen = new Pen(Color.Red, 10))
{
    g1.DrawRectangle(pen, new Rectangle(new Point(100, 100), new Size(200, 200)));
    g2.DrawRectangle(pen, new Rectangle(new Point(100, 100), new Size(200, 200)));
}

DeleteDC(screenDC1);
DeleteDC(screenDC2);

Используя user32.dll и C #, я написал метод, который вы видите ниже. Используя дескриптор процесса для окна, он установит позицию окна в указанном месте {x,y} .

Однако в среде с несколькими мониторами приведенный ниже код устанавливает положение окна только для основного монитора. Я хотел бы иметь возможность выбрать, какой монитор тоже.
Может кто-нибудь объяснить, как это можно сделать с помощью SetWindowPos или, возможно, в сочетании с другой функцией user32.dll ?

[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_SHOWWINDOW = 0x0040;

public static void SetWindowPosition(Process p, int x, int y)
{
    IntPtr handle = p.MainWindowHandle;
    if (handle != IntPtr.Zero)
    {
        SetWindowPos(handle, IntPtr.Zero, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
    }
}

Решение основано на комментарии Джими.

Вот моя конфигурация монитора:

Обратите внимание, что у меня есть дополнительный монитор слева от моего основного монитора. Прочитав ссылку на Виртуальный монитор, предоставленную Джими, я обнаружил, что для перемещения окон на дополнительный монитор я должен использовать отрицательное значение x, поскольку оно слева от источника основного монитора (верхний левый угол или <0,0>).

Поэтому, если я хочу, чтобы в моем положении окна была задана координата <0,0> вторичного монитора, я должен вычесть ширину x вторичного монитора из исходной точки основного монитора, например:

<0,0> - <1920,0> = <-1920,0>

Теперь, когда я вызываю SetWindowPosition в моем клиентском коде, я называю это так:

SetWindowPosition(Process p, -1920, 0);

Примечание: я не знаю, что бы вы сделали, если бы мониторы имели разные разрешения. Это более сложная тема, а не вопрос, который я задаю. Кроме того, я не видел необходимости углубляться в тему, так как простой пример, приведенный выше, решил все мои проблемы.





user32