c# - 在多个显示器上使用 SetWindowPos

标签 c# .net winapi screen user32

使用 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 提供的 Virtual Monitor 链接后,我发现要将窗口移动到辅助监视器,我必须使用负 x 值,因为它位于主监视器原点(左上角,或 (0, 0))的左侧。
因此,如果我想将窗口位置设置为辅助监视器的 <0,0> 坐标,我必须从主监视器的原点减去辅助监视器的 x 宽度,如下所示:
(0, 0) - (1920, 0) = (-1920, 0)
现在,当我拨打 SetWindowPosition在我的客户端代码中,我这样称呼它:
SetWindowPosition(Process p, -1920, 0);
注意:如果显示器的分辨率不同,我不知道你会怎么做。这是一个更复杂的话题,而不是我要问的问题。此外,我认为没有必要深入探讨该主题,因为上面的简单示例解决了我的所有问题。

最佳答案

系统显示配置和 VirtualScreen
在 Windows 系统中,Primary Screen (编程视角)是左上角位置设置为的显示设备。 Point(0,0) .
这意味着显示器位于 主屏幕,将有 X坐标(如果显示为纵向布局,Y 坐标可能为负)。
上的显示右 将有 X坐标(如果显示为纵向布局,Y 坐标可能为负)。
显示在主屏幕左侧 :
换句话说,显示为负 Point.X起源。Point.X origin 是前面所有 Screens[].Width 的总和, 从 Point.X 中减去主屏幕的原点坐标。
显示在主屏幕右侧 :
换句话说,显示为正值 Point.X起源。Point.X origin 是前面所有 Screens[].Width 的总和, 主要包括 , 添加到原点 Point.X主屏幕的坐标。

关于 Dpi 意识的重要说明 :
如果应用程序不是 DPI Aware,则所有这些措施都可能受到系统执行的虚拟化和自动 DPI 缩放的影响。所有度量都将统一为默认的 96 Dpi:应用程序将接收缩放值。这还包括从非 Dpi ware Win32 API 函数中检索到的值。见:
High DPI Desktop Application Development on Windows
中启用对所有目标系统的支持app.manifest 文件,取消注释所需的部分。
添加/取消注释 DpiAware and DpiAwareness sections app.manifest 文件。
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
Multi Display Disposition 1

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

Multi Display Disposition 1
虚拟屏
Virtual Screen 是一种虚拟显示,其尺寸表示为:
产地 :最左边的原点坐标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])
返回 Screen包含指定 Control 的最大部分的对象引用。
Screen.FromHandle([Window Handle])
返回 Screen包含 Handle 引用的 Window\Control 的最大部分的对象
Screen.FromPoint([Point])
返回 Screen包含特定 Point 的对象
Screen.FromRectangle([Rectangle])
返回 Screen包含指定 Rectangle 的最大部分的对象
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将是 Screen代表 \\.\DISPLAY3 的对象属性。screen对象也会报告 \\.\DISPLAY[N] Screen 的名称其中form2显示。

    获取hMonitor Screen 对象的句柄 :
    .NET Reference Source显示 hMonitor 返回调用 [Screen].GetHashCode();
    IntPtr monitorHwnd = new IntPtr([Screen].GetHashCode());
    
    或者使用相同的 native Win32 函数:
    MonitorFromWindow , MonitorFromPointMonitorFromRect
    [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 更改并最终对新设置使用react。

  • 获取屏幕设备上下文的句柄 :
    检索任何可用显示器的 hDC 的通用方法。
    当只需要特定的屏幕引用时,可以使用前面描述的方法之一来确定屏幕坐标或屏幕设备。
    Screen.DeviceName属性可以用作 lpszDriver GDI 的参数 CreateDC功能。它将返回 Graphics.FromHdc 的显示器的 hDC可用于创建有效的 Graphics 对象,该对象将允许在特定屏幕上绘制。
    在这里,假设至少有两个显示器可用:
    [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);
    

    关于c# - 在多个显示器上使用 SetWindowPos,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53012896/

    相关文章:

    c# - 使用 dottrace 查找内存泄漏

    c# - 将控件添加到 TableLayoutPanel 的快速方法

    c++ - 使用 MsiEnumProducts 找不到已安装的 Office 2013 家庭版和企业版

    创建 3 个线程并使用 WSAWaitForMultipleEvents() 和 WSAEnumNetworkEvents() 时 CPU 使用率 100%

    c++ - 创建类似抽屉的窗口

    C#后缀和前缀递增/递减重载区别

    c# - 构建包后通过 URL 在 Win8 商店应用程序中打开文件

    c# - 如何将依赖项注入(inject) asp.net core 中的模型?

    c# - 我是否正确使用了单元测试?

    c# - Will Try Catch effect Performance 如果用于监视不会发生运行时异常的代码