将SetWindowPos与多个监视器一起使用

sap*_*ket 2 c# windows user32

使用user32.dllC#,我编写了下面看到的方法。使用窗口的处理手柄,它将在指定{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);
    }
}
Run Code Online (Sandbox Code Playgroud)

基于Jimi的评论的解决方案。

这是我的显示器配置:

在此处输入图片说明

观察到我的主监视器左侧有一个辅助监视器。阅读Jimi提供的Virtual Monitor链接后,我发现要将窗口移动到次要显示器,我必须使用负x值,因为它位于主要显示器原点的左上角(左上角或<0,0>)。

因此,如果要将窗口位置设置为辅助监视器的<0,0>坐标,则必须从主监视器的原点减去辅助监视器的x宽度,如下所示:

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

现在,当我在客户端代码中调用SetWindowPosition时,我将其命名为:

SetWindowPosition(Process p, -1920, 0);
Run Code Online (Sandbox Code Playgroud)

注意:如果显示器的分辨率不同,我不知道该怎么办。那是一个更复杂的话题,而不是我要问的问题。另外,我认为没有必要深入探讨该主题,因为上面的简单示例解决了我所有的问题。

Jim*_*imi 6

系统显示配置和VirtualScreen

在Windows系统中,主屏幕(编程角度)是显示设备,其左上角位置设置为Point(0,0)

这意味着位于主屏幕左侧的显示器将具有 X坐标(Y如果显示器为纵向布局,则坐标可能为负)。右侧
的显示器将具有坐标(如果显示器为纵向布局,则坐标可能为负)。 XY

在显示器左侧主屏幕的
换句话说,显示有一个的起源 的起源是上述所有的总和,从减去原点主屏幕坐标。 Point.X
Point.XScreens[].WidthPoint.X

在显示主屏
换句话说,显示有一个积极的起源 的起源是所有的总和前面,主要包括,加入到原点主屏幕坐标。 Point.X
Point.XScreens[].WidthPoint.X


重要说明
如果应用程序不是DPIAware,则系统执行的虚拟化和自动DPI缩放可能会损害所有这些措施。所有度量将统一为默认的96 Dpi:应用程序将接收缩放的值。这也包括从非DpiAware Win32 Api函数检索的值。看到:

Windows上的高DPI桌面应用程序开发

启用对app.manifest文件中所有目标系统的支持,而无需注释必需的部分。

在文件中添加/取消注释DpiAware和DpiAwareness部分app.manifest
PerMonitorV2 dpi的意识模式可以在设置app.config文件(可从Windows 10创想版)。


示例:
考虑一个具有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
Run Code Online (Sandbox Code Playgroud)


多重显示配置1


如果我们使用“系统”小程序更改“主屏幕”参考,将其设置为\\.\DISPLAY3,则坐标将相应地进行修改:

多重显示配置1


虚拟屏幕

虚拟屏幕是一个虚拟显示器,其尺寸由以下各项表示:
原点:最左侧Screen
宽度的原点坐标宽度:所有Screens宽度的总和。
高度:最高的高度Screen

这些度量由SystemInformation.VirtualScreen报告
。主屏幕SizeSystemInformation.PrimaryMonitorSize报告。
所有屏幕的当前度量和位置也可以使用Screen.AllScreens并检查每个\\.\DISPLAY[N]属性来检索。

使用前面的示例作为参考,在第一个配置中,VirtualScreen边界为:

Bounds: (-1680, 0, 3280, 1080)  Left: -1680  Right: 3280   Top: 0  Bottom: 1080
Run Code Online (Sandbox Code Playgroud)

在第二种配置中,VirtualScreen界限是:

Bounds: (0, 0, 4960, 1080)  Left: 0  Right: 4960   Top: 0  Bottom: 1080
Run Code Online (Sandbox Code Playgroud)


显示区域内的窗口位置

所述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
- Control引用

要确定\\.\DISPLAY[N]显示当前表单的位置,请调用(例如):

Screen.FromHandle(this);
Run Code Online (Sandbox Code Playgroud)

要确定在哪个屏幕上显示辅助表格:(
使用示例显示示例)

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

screenSize将是=到\\.\DISPLAY3界限。
screen将是Screen代表\\.\DISPLAY3属性的对象。

screen对象还将报告其中显示的\\.\DISPLAY[N]名称。Screenform2


获取hMonitor屏幕对象的句柄

.NET参考源显示,hMonitor返回调用[Screen].GetHashCode();

IntPtr monitorHwnd = new IntPtr([Screen].GetHashCode());
Run Code Online (Sandbox Code Playgroud)

或使用相同的本机Win32函数:

MonitorFromWindowMonitorFromPointMonitorFromRect

[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);
Run Code Online (Sandbox Code Playgroud)

获取屏幕的设备上下文句柄
检索任何可用Display的hDC的通用方法。

当仅需要特定的屏幕参考时,可以使用上述方法之一确定屏幕坐标或屏幕设备。

Screen.DeviceName属性可以用作lpszDriverGDI的参数的CreateDC函数。它将返回Graphics.FromHdc可用于创建有效Graphics对象的显示器的hDC,该对象将允许在特定屏幕上绘画。

在这里,假设至少有两个显示器可用:

[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);
Run Code Online (Sandbox Code Playgroud)