c# - Usando SetWindowPos com vários monitores



windows user32 (1)

Sistema exibe disposição e VirtualScreen

Em um sistema Windows, a tela principal (perspectiva de programação) é o dispositivo de exibição que tem sua posição de canto superior esquerdo definida como Point(0,0) .

Isso implica que os displays posicionados à esquerda da tela principal terão coordenadas X negativas (a coordenada Y poderá ser negativa se a exibição estiver no layout de retrato).
As exibições à direita terão coordenadas X positivas (a coordenada Y poderá ser negativa se a exibição estiver no layout Retrato).

Exibe à esquerda da tela principal :
Em outras palavras, exibe que têm uma origem Point.X negativa
A origem Point.X é a soma de todas as Screens[].Width anteriores Screens[].Width , subtraída da coordenada de origem Point.X da tela principal.

Exibe à direita da tela principal :
Em outras palavras, exibe que têm uma origem Point.X positiva
A origem Point.X é a soma de todas as Screens[].Width anteriores Screens[].Width , incluída Principal , adicionada à coordenada Point.X origem da Tela Principal.

Nota importante :
Se o aplicativo não for o DPIAware, todas essas medidas podem ser comprometidas pela virtualização e pelo dimensionamento automático do DPI realizado pelo sistema. Todas as medidas serão uniformizadas para um padrão de 96 Dpi: a aplicação receberá valores escalonados. Isso também inclui os valores recuperados das funções não-DpiAware Win32 Api. Vejo:

Desenvolvimento de aplicativos de desktop com alto DPI no Windows

Habilite o suporte para todos os sistemas de app.manifest arquivo app.manifest , descomentando as seções necessárias.

Adicione / descomente as seções DpiAware e DpiAwareness no arquivo app.manifest .
O modo PerMonitorV2 Dpi Awareness pode ser definido no arquivo app.config (disponível no Windows 10 Creators Edition).

Exemplo:
Considere um sistema com 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



Se \\.\DISPLAY3 , usando o applet do sistema, a referência da tela principal, configurando-a como \\.\DISPLAY3 , as coordenadas serão modificadas de acordo:


Tela virtual

A tela virtual é uma exibição virtual, cujas dimensões são representadas por:
Origem : a coordenada de origem da Screen mais à esquerda
Largura : a soma de todas as larguras das Screens .
Altura : a altura da Screen mais alta

Essas medidas são relatadas pelo SystemInformation.VirtualScreen
O Size tela principal é relatado por SystemInformation.PrimaryMonitorSize
Todas as medidas e posições atuais das Telas também podem ser recuperadas usando Screen.AllScreens e inspecionando cada uma das propriedades \\.\DISPLAY[N] .

Usando o exemplo anterior como referência, na primeira disposição, os limites do VirtualScreen são:

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

Na segunda disposição, os limites do VirtualScreen são:

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


Posição da janela dentro de uma área de exibição :

A classe Screen oferece vários métodos que podem ser usados ​​para determinar em qual tela uma janela específica é exibida atualmente:

Screen.FromControl([Control reference])
Retorna o objeto Screen que contém a maior seção da referência de Control especificada.

Screen.FromHandle([Window Handle])
Retorna o objeto Screen que contém a maior seção do Window \ Control referenciada por um Handle

Screen.FromPoint([Point])
Retorna o objeto Screen que contém um Point específico

Screen.FromRectangle([Rectangle])
Retorna o objeto Screen que contém a maior seção do Rectangle especificado

Screen.GetBounds() (sobrecarregado)
Retorna uma estrutura de Rectangle que faz referência aos limites de tela que contêm:
- um Point específico
- maior seção do Rectangle especificado
- Uma referência de Control

Para determinar o \\.\DISPLAY[N] no qual o formulário atual é mostrado, chame (por exemplo):

Screen.FromHandle(this);

Para determinar em qual tela um formulário secundário é mostrado:
(Usando a amostra Exibida no exemplo)

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

screenSize será = para \\.\DISPLAY3 Limites.
screen será o objeto Screen representando as propriedades \\.\DISPLAY3 .

screen objeto de screen também relatará o nome \\.\DISPLAY[N] da Screen na qual o form2 é mostrado.

Obtenha o hMonitor de um objeto Screen :

A fonte de referência .NET mostra que o hMonitor é retornado chamando [Screen].GetHashCode();

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

Ou usando as mesmas funções nativas do 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);

Obter um identificador do contexto do dispositivo de uma tela :
Um método genérico para recuperar o hDC de qualquer Display disponível.

As coordenadas da tela ou dispositivo de tela podem ser determinadas usando um dos métodos descritos anteriormente quando somente uma referência de tela específica é necessária.

A propriedade Screen.DeviceName pode ser usada como o parâmetro CreateDC função GDI + CreateDC . Ele retornará o hDC da exibição que Graphics.FromHdc pode usar para criar um objeto Graphics válido que permitirá pintar em uma tela específica.

Aqui, assumindo pelo menos dois monitores estão disponíveis:

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

Usando user32.dll e C # eu escrevi o método que você vê abaixo. Usando um identificador de processo para uma janela, ele definirá a posição da janela em um local {x,y} fornecido.

No entanto, em um ambiente com vários monitores, o código abaixo define apenas a posição da janela para o monitor principal. Eu gostaria de poder selecionar qual monitor também.
Alguém pode por favor explicar como isso pode ser feito usando SetWindowPos ou talvez uma combinação com outra função 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);
    }
}

Solução baseada no comentário de Jimi.

Aqui está a configuração do meu monitor:

Observe que eu tenho um monitor secundário à esquerda do meu monitor principal. Depois de ler o link do Monitor virtual que Jimi forneceu, descobri que, para mover as janelas para o monitor secundário, devo usar um valor x negativo, pois ele é deixado da origem do monitor principal (canto superior esquerdo ou <0,0>).

Portanto, se eu quiser definir minha posição de janela para a coordenada <0,0> do monitor secundário, devo SUBLIGAR a largura x do monitor secundário da origem do monitor principal, desta forma:

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

Agora, quando eu chamo SetWindowPosition no meu código de cliente, eu o chamo assim:

SetWindowPosition(Process p, -1920, 0);

Nota: Eu não sei o que você faria se os monitores tivessem resoluções diferentes. Esse é um tema mais complexo e não uma pergunta que estou fazendo. Além disso, não vi necessidade de explorar mais profundamente o tópico, pois o exemplo simples acima resolveu todos os meus problemas.





user32