Graphics.CopyFromScreen是Windows Forms下最轻量可控的截图方式,适用于局部或定时抓屏,但需注意坐标系、屏幕尺寸、DPI缩放及位图预创建等关键细节。

用 Graphics.CopyFromScreen 截当前屏幕最直接
这是 Windows Forms 下最轻量、最可控的截图方式,不依赖外部库,也不触发 UAC 提权。它本质是把屏幕某块像素“拷贝”到内存位图里,适合做局部截图或定时抓屏。
常见错误是传错坐标:比如把 Point(0, 0) 当成窗口左上角,实际是整个桌面左上角;或者宽高设超了屏幕分辨率,导致抛出 ArgumentException: 参数错误。
- 先用
Screen.PrimaryScreen.Bounds或Screen.AllScreens拿真实屏幕尺寸,别硬写1920x1080 - 目标
Bitmap必须提前创建,且大小和要截的区域一致,否则CopyFromScreen会失败 - 如果截的是带 DPI 缩放的高分屏(比如 125%),
CopyFromScreen默认按物理像素操作,但你的坐标可能是逻辑坐标——得用Graphics.DpiX/Y换算,或改用Graphics.CopyFromScreen的重载加CopyPixelOperation.SourceCopy
示例:截主屏全图
var bounds = Screen.PrimaryScreen.Bounds; using var bitmap = new Bitmap(bounds.Width, bounds.Height); using var g = Graphics.FromImage(bitmap); g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
截指定窗口要用 PrintWindow API 而非 CopyFromScreen
CopyFromScreen 只能截已绘制到桌面的内容,对最小化窗口、被遮挡窗口、或启用了硬件加速(如 WPF/Chrome)的窗口无效。这时候必须调 Windows API 的 PrintWindow。
容易踩的坑是没正确获取窗口句柄(IntPtr):用 Process.MainWindowHandle 可能为空(进程还没显示窗体),或拿到的是子窗口句柄;另外 PrintWindow 对某些 UWP 或以 WS_EX_LAYERED 创建的窗口也无效。
- 优先用
FindWindow或EnumWindows配合类名/标题匹配找句柄,比依赖Process更稳 - 调用前确保目标窗口可见且未最小化,否则返回黑图
- 必须在目标窗口线程上下文外调用(即不能在它的 UI 线程里同步调),否则可能死锁
- 截完记得用
DestroyWindow清理临时窗口(如果自己创建了)
保存为 PNG 时别漏掉 ImageFormat.Png 参数
Bitmap.Save("a.jpg") 看似能运行,但实际保存的是无压缩 BMP 格式——文件巨大,且扩展名和内容不一致。C# 的 Save 方法默认按扩展名猜测格式,但不可靠,尤其在服务器环境或自定义流中。
更隐蔽的问题是 Alpha 通道:如果截图含透明区域(比如截了半透明窗体),用 JPEG 保存会强制丢弃透明度,变成白底;而 PNG 才真正支持。
- 显式传
ImageFormat.Png,不依赖文件后缀 - 若需压缩质量控制(如转 JPEG),用
EncoderParameters配Encoder.Quality,别直接写.jpg - 保存到
MemoryStream时,务必设stream.Position = 0再读取,否则后续解码失败
多显示器环境下 Screen.AllScreens 返回顺序不固定
你不能假设 Screen.AllScreens[0] 是主屏,也不能认为数组按从左到右排列。Windows 不保证这个顺序,尤其热插拔显示器后,序号可能重排。
典型现象:代码在开发机跑得好好的,部署到客户机器就只截到一半屏幕,或者总截错显示器。
- 用
screen.Primary判断是否为主屏,而不是看索引 - 需要按物理位置拼接多屏图时,比较
Bounds.X和Bounds.Y,而非数组下标 - 如果要截“鼠标所在屏”,查
Cursor.Position落在哪个Screen.Bounds内,别用Screen.FromControl(this)这种依赖当前窗体位置的方式
截图这事,表面只是几行代码,但坐标的语义、DPI 的层级、窗口的状态、多屏的拓扑——每个点都卡在系统行为边界上。稍不注意,就变成“本地能跑,换台机器就黑图”。










