Graphics.CopyFromScreen是WinForms下最轻量的截图方案,需处理DPI感知、多屏坐标及保存异常;WPF应优先用RenderTargetBitmap截可视树,真屏幕截图须在STA线程调CopyFromScreen。

用 Graphics.CopyFromScreen 截当前屏幕最直接
这是 WinForms 下最轻量、无需第三方库的方案,适合截整个主屏或指定区域。核心是创建一个 Bitmap,用 Graphics 从屏幕拷贝像素过去。
常见错误是忽略 DPI 缩放导致截图模糊或偏移——Windows 高 DPI 设置下,CopyFromScreen 默认按物理像素操作,但窗体坐标可能是逻辑像素。务必先调用 SetProcessDpiAwarenessContext 或确保应用 manifest 声明 DPI 感知。
- 截全屏:源坐标
(0, 0),宽高用System.Windows.Forms.Screen.PrimaryScreen.Bounds - 截活动窗口:用
GetForegroundWindow+GetWindowRect获取位置,再传给CopyFromScreen - 保存格式选
ImageFormat.Png避免 JPEG 有损压缩,尤其含文字或 UI 边框时
用 Graphics.CopyFromScreen 截多显示器需手动遍历
默认只截主屏。Screen.AllScreens 返回所有显示器数组,每个 Screen 对象带 Bounds 和 Primary 标志。想拼接全景图,得为每块屏幕单独截图再合并。
容易踩的坑是直接用 Screen.Bounds 当作全局坐标系——实际上各屏幕的 Bounds.X/Y 已是相对于虚拟桌面左上角的偏移,可直接用于 CopyFromScreen 的 sourceX/sourceY 参数。
- 遍历
Screen.AllScreens,对每个s创建new Bitmap(s.Bounds.Width, s.Bounds.Height) g.CopyFromScreen(s.Bounds.X, s.Bounds.Y, 0, 0, s.Bounds.Size)- 合并时用新
Bitmap和Graphics.DrawImage按各屏幕Bounds定位粘贴
WPF 项目不能直接用 Graphics.CopyFromScreen?用 RenderTargetBitmap 替代
WPF 渲染走 DirectX,Graphics 是 GDI+,二者不兼容。强行调用会抛 InvalidOperationException:“调用线程必须为 STA” 或 “无法从非 UI 线程访问”。正确做法是渲染目标到内存位图。
RenderTargetBitmap 截的是 WPF 可视树,不是真实屏幕——它不捕获系统托盘、其他窗口、鼠标指针。如需真屏幕截图,仍得回退到 CopyFromScreen,但必须在 STA 线程(如 Dispatcher)中调用。
- 截 WPF 窗口自身:用
VisualTreeHelper.GetDescendantBounds(this)得尺寸,构造RenderTargetBitmap后Render(this) - 截整个屏幕(含非 WPF 内容):用
Dispatcher.Invoke包裹Graphics.CopyFromScreen调用 - 注意
RenderTargetBitmap构造参数单位是 device-independent units(1/96 英寸),需乘VisualTreeHelper.GetDpi(this).PixelsPerInchX / 96.0换算
保存失败常见原因和绕过方法
最常遇到 System.Runtime.InteropServices.ExternalException: GDI+ 中发生一般性错误,基本都和文件路径、权限、编码或并发写入有关。
关键点:保存路径不能含中文或特殊符号(即使 .NET 支持,GDI+ 底层可能崩);目标文件夹必须存在且进程有写权限;同一 Bitmap 不能被多个线程同时 Save。
- 用
Path.GetTempFileName()生成安全路径测试是否路径问题 - 保存前加
Directory.CreateDirectory(Path.GetDirectoryName(filePath)) - 避免直接
bitmap.Save(filePath),改用using (var fs = new FileStream(...)) bitmap.Save(fs, ImageFormat.Png)控制流生命周期 - 如果截图后立刻保存报错,尝试在
Save前调用bitmap.RotateFlip(RotateFlipType.RotateNoneFlipNone)强制触发内部缓冲刷新
真正难的不是调哪个 API,而是搞清你要截的是“WPF 界面渲染结果”、“Windows 桌面合成画面”,还是“某个 HWND 对应的窗口客户区”——三者技术路径完全不同,混用必出错。


















