Использование SetWindowPos с несколькими мониторами - PullRequest
0 голосов
/ 26 октября 2018

Используя 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);
    }
}

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

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

enter image description here

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

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

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

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

SetWindowPosition(Process p, -1920, 0);

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

1 Ответ

0 голосов
/ 28 октября 2018

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

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

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

Отображение на влево основного экрана :
Другими словами, дисплеи, имеющие отрицательное Point.X происхождение
Начало координат Point.X представляет собой сумму всех предыдущих Screens[].Width, вычтенных из координат начала координат 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


Multi Display Disposition 1


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

Multi Display Disposition 1


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

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

Об этих показателях сообщает SystemInformation.VirtualScreen
Основной экран Size сообщается 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
- A 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 Границы.
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 можно использовать в качестве параметра lpszDriver функции 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);
...