Usando SetWindowPos con múltiples monitores

Resuelto sapbucket asked hace 6 años • 1 respuestas

Usando user32.dlly 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 SetWindowPoso quizás una combinación con otra user32.dllfunció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:

ingrese la descripción de la imagen aquí

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 SetWindowPositionmi 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.

sapbucket avatar Oct 26 '18 23:10 sapbucket
Aceptado

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 Ycoordenada podría ser negativa si la pantalla está en formato vertical).
Las Pantallas de la derecha tendrán coordenadas positivas X (la Ycoordenada 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.Xorigen es la suma de todo lo anterior Screens[].Width, restado de la Point.Xcoordenada 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.Xorigen es la suma de todo lo anterior Screens[].Width, incluido el Primario , agregado a la Point.Xcoordenada 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.manifestarchivo, descomentando las secciones requeridas.

.NET Framework anterior a 4.7.2 :
agregue o descomente las secciones DpiAware y DpiAwareness en el app.manifestarchivo.

.NET Framework anterior a 4.7.2+ : el modo de reconocimiento de ppp de PerMonitorV2 se puede configurar en el app.configarchivo (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.manifestsolo se usa para especificar qué versiones del sistema admite su aplicación. app.configno 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

Disposición de pantalla múltiple 1

Si cambiamos, usando el subprograma Sistema, la referencia de la Pantalla Primaria, estableciéndola en \\.\DISPLAY3, las coordenadas se modificarán en consecuencia:

Disposición de pantalla múltiple 1

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 ScreensAnchos.
Altura : la Altura del más alto Screen.

Estas medidas se informan mediante SystemInformation.VirtualScreen
La pantalla principal Sizese 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 VirtualScreenlímites son:

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

En la disposición segunda, los VirtualScreenlí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 Screenobjeto que contiene la sección más grande de la Controlreferencia especificada.

Screen.FromHandle([Window Handle])
Devuelve el Screenobjeto que contiene la sección más grande de Window\Control al que hace referencia unHandle

Screen.FromPoint([Point])
Devuelve el Screenobjeto que contiene un específicoPoint

Screen.FromRectangle([Rectangle])
Devuelve el Screenobjeto que contiene la sección más grande del especificadoRectangle

Screen.GetBounds()(sobrecargado)
Devuelve una Rectangleestructura que hace referencia a los límites de pantalla que contienen:

  • especificoPoint
  • sección más grande del especificadoRectangle
  • Una Controlreferencia

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

screenSizeserá igual a los \\.\DISPLAY3límites.
screenserá el Screenobjeto que representa las \\.\DISPLAY3propiedades.

screenEl objeto también informará el \\.\DISPLAY[N]nombre del objeto Screenen el que form2se muestra.


Obtener el hMonitoridentificador de un objeto Screen :

La fuente de referencia de .NET muestra que se hMonitordevuelve 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_WINDOWPOSCHANGEDmensajes, llamar MonitoFromWindowy 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 MONITORINFOREXasí:
( dwFlagsse establece en 0x00000001si 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];
}
Jimi avatar Oct 27 '2018 22:10 Jimi