Screen.PrimaryScreen.Bounds 返回主显示器的缩放后显示区域矩形(含任务栏),单位为逻辑像素,已适配DPI缩放;AllScreens[0]按枚举顺序排列,未必是主屏,需用PrimaryScreen确保正确性。

Screen.PrimaryScreen.Bounds 返回的是什么
它返回的是主显示器的显示区域矩形,Bounds 包含 X、Y、Width、Height —— 注意,这四个值是「像素单位」,且已考虑系统缩放(DPI scaling),不是原始物理分辨率。
常见错误现象:Screen.PrimaryScreen.Bounds.Width 在 150% 缩放的 4K 屏上可能返回 2560,而不是 3840;有人误以为这是“获取不到真实分辨率”,其实是设计如此。
- 如果要拿 Windows 原生 DPI 下的物理像素尺寸,得用
Screen.PrimaryScreen.DeviceName配合GetDeviceCaps(Win32 API) -
Bounds是最常用、最安全的选择,适用于 UI 布局、窗口居中等场景 - 在多屏且主屏被手动切换后,
PrimaryScreen会动态变化,但不会自动刷新 —— 需监听SystemEvents.DisplaySettingsChanged事件才可感知
为什么 Screen.AllScreens[0].Bounds 不等于 PrimaryScreen.Bounds
因为 Screen.AllScreens 按物理连接顺序或枚举顺序排列,[0] 不一定是主屏;而 PrimaryScreen 是系统标记的“主显示器”,通常对应任务栏所在屏。
使用场景:你想把窗口弹到“用户默认工作区”,就该用 PrimaryScreen;但你想按左→右顺序遍历所有屏做分屏投射,就得用 AllScreens 并自己排序(比如按 Bounds.X)。
- 别硬写
AllScreens[0]替代PrimaryScreen,行为不可靠 -
AllScreens可能为空(极少见,如远程桌面无显卡),访问前务必判空 - 某些 Kiosk 或嵌入式环境里,
PrimaryScreen可能为null,需加空引用检查
高 DPI 模式下 Bounds 和 WorkingArea 的区别
Bounds 是整个屏幕可绘区域(含任务栏),WorkingArea 是扣除任务栏、停靠工具栏后的可用客户区 —— 两者在高 DPI 下都已缩放适配,无需手动除以缩放比。
性能 / 兼容性影响:二者都是属性访问,无 IO、无 P/Invoke,毫秒级;但 WorkingArea 在任务栏自动隐藏时会动态变化,Bounds 则稳定。
- 弹窗居中显示,推荐用
PrimaryScreen.WorkingArea中心点,避免被任务栏遮挡 - 全屏覆盖(如取色器、录屏 overlay),必须用
Bounds,否则边缘留黑边 - 不要对
WorkingArea的Height做固定减法(比如 -40)来“模拟任务栏高度”,不同系统版本任务栏高度不同
WPF 或 WinForms 中直接用 Screen 类的安全前提
Screen 类属于 System.Windows.Forms,哪怕你在 WPF 项目里用,也得引用 System.Windows.Forms.dll 并确保线程上下文支持 —— 它依赖 Win32 EnumDisplayMonitors,必须在 STA 线程调用。
常见错误现象:控制台应用或后台线程里直接调 Screen.PrimaryScreen,返回 null 或抛 InvalidOperationException。
- WinForms 项目默认满足 STA,没问题
- WPF 主线程是 STA,可用;但若从
Task.Run里调,必须显式设[STAThread]或用Dispatcher.Invoke - .NET 6+ 控制台默认是 MTA,不加配置就调会失败 —— 别指望“偶尔能跑通”,这是未定义行为
真正容易被忽略的是:多显示器环境下,“主屏”这个概念由 Windows 图形子系统维护,C# 层只是读取快照;系统设置变更(比如拔掉主屏、重排显示器)后,代码不会自动感知,得自己挂钩事件或定期轮询 —— 这点连很多老项目都没处理。











