Usando SetWindowPos con múltiples monitores
Usando user32.dll
y C# escribí el método que ves a continuación. Usando un identificador de proceso para una ventana, establecerá la posición de la ventana en una (x, y)
ubicación proporcionada.
Sin embargo, en un entorno de múltiples monitores, el siguiente código establece la posición de la ventana únicamente en el monitor principal. También me gustaría poder seleccionar qué monitor.
¿Alguien puede explicar cómo se puede lograr esto usando SetWindowPos
o quizás una combinación con otra user32.dll
función?
[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);
}
}
Solución basada en el comentario de Jimi.
Aquí está la configuración de mi monitor:
Observe que tengo un monitor secundario a la izquierda de mi monitor principal. Después de leer el enlace del Monitor virtual que me proporcionó Jimi, descubrí que para mover ventanas al monitor secundario debo usar un valor x negativo porque está a la izquierda del origen del monitor principal (esquina superior izquierda o (0, 0)
).
Por lo tanto, si quiero que la posición de mi ventana esté configurada en la coordenada <0,0> del monitor secundario, debo RESTAR el ancho x del monitor secundario del origen del monitor principal, de esta manera:
(0, 0) - (1920, 0) = (-1920, 0)
Ahora, cuando introduzco SetWindowPosition
mi código de cliente, lo llamo así:
SetWindowPosition(Process p, -1920, 0);
Nota: No sé qué harías si los monitores tuvieran resoluciones diferentes. Ese es un tema más complejo y no una pregunta que estoy haciendo. Además, no vi la necesidad de explorar más profundamente el tema ya que el simple ejemplo anterior resolvió todos mis problemas.
Disposición de Displays del Sistema y VirtualScreen
En un sistema Windows, la pantalla principal (perspectiva de programación) es el dispositivo de visualización que tiene su posición en la esquina superior izquierda establecida en Point(0,0)
.
Esto implica que las pantallas ubicadas a la izquierda de la pantalla principal tendrán coordenadas negativas X
(la Y
coordenada podría ser negativa si la pantalla está en formato vertical).
Las Pantallas de la derecha tendrán coordenadas positivas X
(la Y
coordenada podría ser negativa si la Pantalla está en formato vertical).
Displays a la Izquierda de la Pantalla Primaria :
En otras palabras, Displays que tienen un Point.X
origen negativo .
El Point.X
origen es la suma de todo lo anterior Screens[].Width
, restado de la Point.X
coordenada de origen de la pantalla principal.
Displays a la derecha de la pantalla principal :
En otras palabras, Displays que tienen un Point.X
origen positivo .
El Point.X
origen es la suma de todo lo anterior Screens[].Width
, incluido el Primario , agregado a la Point.X
coordenada de origen de la Pantalla Primaria.
Nota importante sobre el reconocimiento de DPI :
si la aplicación no admite DPI, todas estas medidas pueden verse comprometidas por la virtualización y el escalado automático de DPI realizado por el sistema. Todas las medidas se uniformarán a un valor predeterminado de 96 Dpi: la aplicación recibirá valores escalados. Esto también incluye los valores recuperados de las funciones API de Win32 que no son Dpi. Ver:
Desarrollo de aplicaciones de escritorio de alto DPI en Windows
Habilite la compatibilidad con todos los sistemas de destino en el app.manifest
archivo, descomentando las secciones requeridas.
.NET Framework anterior a 4.7.2 :
agregue o descomente las secciones DpiAware y DpiAwareness en el app.manifest
archivo.
.NET Framework anterior a 4.7.2+ : el modo de reconocimiento de ppp de PerMonitorV2 se puede configurar en el app.config
archivo (disponible en Windows 10 Creators Edition).
.NET6+ :
agregue o edite la <ApplicationHighDpiMode>
propiedad en el archivo del proyecto ( .csproj
), por ejemplo:
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
Para conocer todas las configuraciones posibles, consulte la referencia de MSBuild para proyectos de .NET Desktop SDK.
En este escenario, app.manifest
solo se usa para especificar qué versiones del sistema admite su aplicación. app.config
no se utiliza.
Ver también:
DPI y píxeles independientes del dispositivo
Escalado de DPI en modo mixto y API compatibles con DPI
Ejemplo:
Considere un sistema con 3 monitores:
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
Si cambiamos, usando el subprograma Sistema, la referencia de la Pantalla Primaria, estableciéndola en \\.\DISPLAY3
, las coordenadas se modificarán en consecuencia:
Pantalla Virtual
La Pantalla Virtual es una pantalla virtual, cuyas dimensiones están representadas por:
Origen : la coordenada de origen del Screen
Ancho más a la izquierda : la suma de todos los Screens
Anchos.
Altura : la Altura del más alto Screen
.
Estas medidas se informan mediante SystemInformation.VirtualScreen
La pantalla principal Size
se informa mediante SystemInformation.PrimaryMonitorSize
Todas las medidas y posiciones actuales de las pantallas también se pueden recuperar usando Screen.AllScreens e inspeccionando cada \\.\DISPLAY[N]
propiedad.
Tomando como referencia el ejemplo anterior, en la disposición primera, los VirtualScreen
límites son:
Bounds: (-1680, 0, 3280, 1080) Left: -1680 Right: 3280 Top: 0 Bottom: 1080
En la disposición segunda, los VirtualScreen
límites son:
Bounds: (0, 0, 4960, 1080) Left: 0 Right: 4960 Top: 0 Bottom: 1080
Posición de la ventana dentro de un área de visualización :
La clase Screen ofrece múltiples métodos que se pueden usar para determinar en qué pantalla se muestra actualmente una ventana específica:
Screen.FromControl([Control reference])
Devuelve el Screen
objeto que contiene la sección más grande de la Control
referencia especificada.
Screen.FromHandle([Window Handle])
Devuelve el Screen
objeto que contiene la sección más grande de Window\Control al que hace referencia unHandle
Screen.FromPoint([Point])
Devuelve el Screen
objeto que contiene un específicoPoint
Screen.FromRectangle([Rectangle])
Devuelve el Screen
objeto que contiene la sección más grande del especificadoRectangle
Screen.GetBounds()
(sobrecargado)
Devuelve una Rectangle
estructura que hace referencia a los límites de pantalla que contienen:
- especifico
Point
- sección más grande del especificado
Rectangle
- Una
Control
referencia
Para determinar \\.\DISPLAY[N]
en qué se muestra el formulario actual, llame (por ejemplo):
Screen.FromHandle(this);
Para determinar en qué pantalla se muestra un formulario secundario:
(Usando el diseño de Pantallas que se muestra en las imágenes de muestra)
var f2 = new Form2();
f2.Location = new Point(-1400, 100);
f2.Show();
Rectangle screenSize = Screen.GetBounds(f2);
Screen screen = Screen.FromHandle(f2.Handle);
screenSize
será igual a los \\.\DISPLAY3
límites.
screen
será el Screen
objeto que representa las \\.\DISPLAY3
propiedades.
screen
El objeto también informará el \\.\DISPLAY[N]
nombre del objeto Screen
en el que form2
se muestra.
Obtener el hMonitor
identificador de un objeto Screen :
La fuente de referencia de .NET muestra que se hMonitor
devuelve llamando[Screen].GetHashCode();
IntPtr monitorHwnd = new IntPtr([Screen].GetHashCode());
O usando las mismas funciones nativas de Win32:
MonitorFromWindow , MonitorFromPoint y 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);
- Para detectar movimientos de ventanas entre monitores, puede manejar
WM_WINDOWPOSCHANGED
mensajes, llamarMonitoFromWindow
y luego GetScaleFactorForMonitor para determinar si hay un cambio de DPI y eventualmente reaccionar a una nueva configuración.
Obtener un identificador del contexto del dispositivo de una pantalla :
un método genérico para recuperar el hDC de cualquier pantalla disponible.
Las coordenadas de la pantalla o el dispositivo de la pantalla se pueden determinar utilizando uno de los métodos descritos anteriormente cuando solo se requiere una referencia de pantalla específica.
La propiedad Screen.DeviceName , que se recupera llamando a GetMonitorInfo() , pasando una estructura MONITORINFOEX (consulte la declaración en la parte inferior) se puede utilizar como parámetro de la función CreateDClpszDriver
de GDI . Devolverá el hDC de la pantalla que Graphics.FromHdc puede usar para crear un objeto Graphics válido, que permitirá pintar en una pantalla específica.
Aquí, suponiendo que haya al menos dos pantallas disponibles:
[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);
Declare MONITORINFOREX
así:
( dwFlags
se establece en 0x00000001
si es el monitor principal)
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public unsafe struct MONITORINFOEXW
{
public uint cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
public fixed char szDevice[32];
}