系统显示配置和虚拟屏幕
在 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报告
主屏幕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])
返回包含指定 Control 引用的最大部分的 Screen 对象。
Screen.FromHandle([Window Handle])
返回包含 Handle 引用的 Window\Control 的最大部分的 Screen 对象
Screen.FromPoint([Point])
返回包含特定 Point 的 Screen 对象
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 函数:
MonitorFromWindow、MonitorFromPoint 和 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);
获取屏幕的设备上下文句柄:
检索任何可用显示器的 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);