【问题标题】:Using SetWindowPos with multiple monitors将 SetWindowPos 与多个监视器一起使用
【发布时间】:2019-03-31 11:02:57
【问题描述】:

使用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 的评论。

这是我的显示器配置:

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

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

(0, 0) - (1920, 0) = (-1920, 0)

现在,当我在客户端代码中调用 SetWindowPosition 时,我会这样称呼它:

SetWindowPosition(Process p, -1920, 0);

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

【问题讨论】:

    标签: c# .net winapi screen user32


    【解决方案1】:

    系统显示配置和虚拟屏幕

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

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

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

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


    关于 Dpi 意识的重要说明
    如果应用程序不支持 DPI,则所有这些措施都可能受到系统执行的虚拟化和自动 DPI 缩放的影响。所有度量都将统一为默认的 96 Dpi:应用程序将接收缩放值。这还包括从非 Dpi ware Win32 API 函数检索的值。见:

    High DPI Desktop Application Development on Windows

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

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

    另见:

    DPI and Device-Independent Pixels
    Mixed-Mode DPI Scaling and DPI-aware APIs


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

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

    虚拟屏幕

    Virtual Screen是一个虚拟显示器,其尺寸表示为:
    Origin:最左边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
    

    在第二个配置中,VirtualScreen 边界是:

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

    显示区域内的窗口位置

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

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

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

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

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

    Screen.GetBounds()(重载)
    返回一个 Rectangle 结构,该结构引用包含以下内容的屏幕边界:

    • 特定的Point
    • 指定Rectangle的最大部分
    • Control 参考

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

    Screen.FromHandle(this);
    

    要确定在哪个屏幕中显示辅助表单:
    (使用示例图像中显示的显示布局)

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

    screenSize 将等于\\.\DISPLAY3 边界。
    screen 将是代表\\.\DISPLAY3 属性的Screen 对象。

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


    获取屏幕对象的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);
    
    • 要检测监视器之间的窗口移动,您可以处理 WM_WINDOWPOSCHANGED 消息,调用 MonitoFromWindow,然后调用 GetScaleFactorForMonitor 以确定是否存在 DPI 更改并最终对新设置做出反应。

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

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

    Screen.DeviceName 属性可用作 GDI 的 CreateDC 函数的 lpszDriver 参数。它将返回Graphics.FromHdc 可用于创建有效图形对象的显示器的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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多