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

问题描述 投票:1回答:1

使用user32.dll和C#我编写了你在下面看到的方法。使用窗口的进程句柄,它将窗口位置设置在提供的{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);
    }
}

解决方案基于Jimi的评论。

这是我的显示器配置:

enter image description here

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

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

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

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

SetWindowPosition(Process p, -1920, 0);

注意:如果显示器具有不同的分辨率,我不知道你会怎么做。这是一个更复杂的话题,而不是我要问的问题。此外,我没有看到有必要深入探讨这个主题,因为上面的简单例子解决了我的所有问题。

c# windows user32
1个回答
3
投票

系统显示处置和VirtualScreen

在Windows系统中,Primary Screen(编程透视图)是显示设备,其左上角位置设置为Point(0,0)

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

显示在主屏幕的左侧: 换句话说,具有负Point.X原点的显示 Point.X原点是所有前面的Screens[].Width的总和,从主屏幕的Point.X原点坐标中减去。

显示在主屏幕的右侧: 换句话说,具有正Point.X原点的显示器 Point.X原点是所有前面的Screens[].Width的总和,包括Primary,添加到主屏幕的原点Point.X坐标。


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

High DPI Desktop Application Development on Windows

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

DpiAware and DpiAwareness sections文件中添加/取消注释app.manifestPerMonitorV2 Dpi Awareness模式可以在app.config文件中设置(可从Windows 10 Creators Edition获得)。


例: 考虑一个带有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

Multi Display Disposition 1

如果我们使用System applet更改主屏幕参考,将其设置为\\.\DISPLAY3,则相应地修改坐标:

Multi Display Disposition 1

虚拟屏幕

虚拟屏幕是虚拟显示,其尺寸表示为: 原点:最左边的Screen的原点坐标 宽度:所有Screens宽度的总和。 身高:最高的Screen的高度

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

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

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

在第二种情况下,VirtualScreen界限是:

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

显示区域内的窗口位置:

Screen class提供了多种方法,可用于确定当前显示特定窗口的屏幕:

Screen.FromControl([Control reference]) 返回包含指定Screen引用的最大部分的Control对象。

Screen.FromHandle([Window Handle]) 返回包含Screen引用的Window \ Control的最大部分的Handle对象

Screen.FromPoint([Point]) 返回包含特定ScreenPoint对象

Screen.FromRectangle([Rectangle]) 返回包含指定Screen的最大部分的Rectangle对象

Screen.GetBounds()(超载) 返回引用包含以下内容的Screen Bounds的Rectangle结构: - 特定的Point - 指定Rectangle的最大部分 - Control参考

要确定显示当前表格的\\.\DISPLAY[N],请致电(例如):

Screen.FromHandle(this);

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

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

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

screen对象还将报告\\.\DISPLAY[N]Screen名称,其中显示form2


获取Screen对象的hMonitor句柄:

.NET Reference Source显示hMonitor返回呼叫[Screen].GetHashCode();

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

或者使用相同的本机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);

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

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

Screen.DeviceName属性可以用作GDI + lpszDriver函数的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);
© www.soinside.com 2019 - 2024. All rights reserved.