Utilizzo di SetWindowPos con più monitor
Usando user32.dlle C # ho scritto il metodo che vedi sotto. Utilizzando un handle di processo per una finestra, imposterà la posizione della finestra in una (x, y)posizione fornita .
Tuttavia, in un ambiente multi-monitorato, il codice seguente imposta la posizione della finestra solo sul monitor principale. Vorrei poter selezionare anche quale monitor.
Qualcuno può spiegare come questo può essere realizzato utilizzando SetWindowPoso forse una combinazione con un'altra user32.dllfunzione?
[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);
}
}
Soluzione basata sul commento di Jimi.
Ecco la mia configurazione del monitor:
Osserva che ho un monitor secondario a sinistra del mio monitor principale. Dopo aver letto il collegamento al monitor virtuale fornito da Jimi, ho scoperto che per spostare le finestre sul monitor secondario devo usare un valore x negativo perché è a sinistra dell'origine del monitor principale (angolo in alto a sinistra, o (0, 0)).
Pertanto, se voglio che la mia posizione della finestra sia impostata sulla coordinata <0,0> del monitor secondario, devo SOTTRARRE la larghezza x del monitor secondario dall'origine del monitor principale, in questo modo:
(0, 0) - (1920, 0) = (-1920, 0)
Ora, quando chiamo il SetWindowPositionmio codice client, lo chiamo in questo modo:
SetWindowPosition(Process p, -1920, 0);
Nota: non so cosa faresti se i monitor avessero risoluzioni diverse. Questo è un argomento più complesso e non una domanda che sto facendo. Inoltre, non ho visto la necessità di approfondire l'argomento poiché il semplice esempio sopra ha risolto tutti i miei problemi.
Risposte
Sistema Visualizza la disposizione e VirtualScreen
In un sistema Windows, la schermata principale (prospettiva di programmazione) è il dispositivo di visualizzazione la cui posizione nell'angolo superiore sinistro è impostata su Point(0,0).
Ciò implica che i display posizionati a sinistra dello schermo principale avranno coordinate negative X (la Ycoordinata potrebbe essere negativa se il display è in layout verticale).
I display a destra avranno coordinate positive X (la Ycoordinata potrebbe essere negativa se il display è in layout verticale).
Display a sinistra dello schermo principale :
in altre parole, display con origine negativa .
L' origine è la somma di tutti i precedenti , sottratta dalla coordinata di origine della schermata principale. Point.X Point.XScreens[].WidthPoint.X
Consente di visualizzare sul destro dello schermo primario :
In altre parole, display che hanno una positiva origine .
L' origine è la somma di tutti i precedenti , Primario incluso , aggiunto alla coordinata di origine della schermata principale. Point.X Point.XScreens[].WidthPoint.X
Nota importante su Dpi Awareness :
se l'applicazione non è DPIAware, tutte queste misure possono essere compromesse dalla virtualizzazione e dal ridimensionamento DPI automatico eseguito dal sistema. Tutte le misure verranno uniformate a un valore predefinito di 96 Dpi: l'applicazione riceverà valori scalati. Ciò include anche i valori recuperati da funzioni non DpiAware Win32 Api. Vedere:
Sviluppo di applicazioni desktop ad alta risoluzione su Windows
Abilita il supporto per tutti i sistemi mirati nel app.manifestfile, rimuovendo il commento dalle sezioni richieste.
Aggiungi / Rimuovi il commento dalle sezioni DpiAware e DpiAwareness nel app.manifestfile.
La modalità PerMonitorV2 Dpi Awareness può essere impostata nel app.configfile (disponibile da Windows 10 Creators Edition).
Guarda anche:
DPI DPI e pixel indipendenti dal dispositivo
Scalabilità DPI in modalità mista e API compatibili con DPI
Esempio:
considera un sistema con 3 monitor:
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
Se cambiamo, tramite l'applet di Sistema, il riferimento della Schermata Principale, impostandolo su \\.\DISPLAY3, le coordinate verranno modificate di conseguenza:
Schermo virtuale
Lo schermo virtuale è un display virtuale, le cui dimensioni sono rappresentate da:
Origine : la coordinata di origine della Screen
larghezza più a sinistra : la somma di tutte le larghezzeScreens .
Altezza : l'altezza del più altoScreen
Queste misure sono riportate da SystemInformation.VirtualScreen
La schermata principale Sizeè riportata da SystemInformation.PrimaryMonitorSize
Tutte le misure e la posizione correnti di Screens possono anche essere recuperate usando Screen.AllScreens e ispezionando ogni \\.\DISPLAY[N]proprietà.
Utilizzando l'esempio precedente come riferimento, nella prima disposizione i VirtualScreenlimiti sono:
Bounds: (-1680, 0, 3280, 1080) Left: -1680 Right: 3280 Top: 0 Bottom: 1080
Nella seconda disposizione, i VirtualScreenlimiti sono:
Bounds: (0, 0, 4960, 1080) Left: 0 Right: 4960 Top: 0 Bottom: 1080
Posizione della finestra all'interno di un'area di visualizzazione :
La classe Screen offre più metodi che possono essere utilizzati per determinare in quale schermata è attualmente visualizzata una finestra specifica:
Screen.FromControl([Control reference])
Restituisce l' Screenoggetto che contiene la sezione più grande del Controlriferimento specificato .
Screen.FromHandle([Window Handle])
Restituisce l' Screenoggetto che contiene la sezione più grande di Window \ Control a cui fa riferimento un fileHandle
Screen.FromPoint([Point])
Restituisce l' Screenoggetto che contiene uno specificoPoint
Screen.FromRectangle([Rectangle])
Restituisce l' Screenoggetto che contiene la sezione più grande dell'oggetto specificatoRectangle
Screen.GetBounds()(sovraccarico)
Restituisce una Rectanglestruttura che fa riferimento ai limiti dello schermo che contengono:
- uno specifico Point
- la sezione più grande del specificato Rectangle
- un Controlriferimento
Per determinare \\.\DISPLAY[N]in cui viene visualizzato il modulo corrente, chiamare (ad esempio):
Screen.FromHandle(this);
Per determinare in quale schermata viene visualizzato un modulo secondario:
(utilizzando i display di esempio nell'esempio)
form2 = new Form2();
form2.Location = new Point(-1400, 100);
form2.Show();
Rectangle screenSize = Screen.GetBounds(form2);
Screen screen = Screen.FromHandle(form2.Handle);
screenSizesarà = a \\.\DISPLAY3Bounds.
screensarà l' Screenoggetto che rappresenta le \\.\DISPLAY3proprietà.
screenoggetto riporterà anche il \\.\DISPLAY[N]nome del Screenin cui form2è mostrato.
Ottieni la hMonitormaniglia di un oggetto Screen :
La sorgente di riferimento .NET mostra che hMonitorviene restituita la chiamata[Screen].GetHashCode();
IntPtr monitorHwnd = new IntPtr([Screen].GetHashCode());
O utilizzando le stesse funzioni native di Win32:
MonitorFromWindow , MonitorFromPoint e 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);
- Per rilevare i movimenti della finestra tra i monitor, è possibile gestire i
WM_WINDOWPOSCHANGEDmessaggi, chiamareMonitoFromWindow, quindi GetScaleFactorForMonitor per determinare se c'è una modifica DPI e, eventualmente, reagire a una nuova impostazione.
Ottieni un handle del contesto del dispositivo di uno schermo :
un metodo generico per recuperare l'hDC di qualsiasi display disponibile.
Le coordinate dello schermo o il dispositivo dello schermo possono essere determinate utilizzando uno dei metodi descritti in precedenza quando è richiesto solo un riferimento dello schermo specifico.
La proprietà Screen.DeviceName può essere utilizzata come lpszDriverparametro della funzione CreateDC di GDI . Restituirà l'hDC del display che Graphics.FromHdc può utilizzare per creare un oggetto Graphics valido, che consentirà di dipingere su uno schermo specifico.
Qui, supponendo che siano disponibili almeno due display:
[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);