Graphics.CopyFromScreen截全屏需适配高DPI:用Screen.PrimaryScreen.Bounds获取逻辑分辨率,按缩放比预分配Bitmap;截窗口需GetWindowRect获取屏幕坐标;必须在STA线程调用,保存用PNG格式并指定路径权限。

用 Graphics.CopyFromScreen 截全屏最直接,但要注意设备缩放比例
Windows 高 DPI 屏幕(比如 125%、150% 缩放)下,Graphics.CopyFromScreen 默认按物理像素抓图,会导致截图内容被“放大裁剪”或位置偏移。这不是 bug,而是 GDI+ 没自动适配 DPI 缩放。
实操建议:
- 先调用
System.Windows.Forms.Screen.PrimaryScreen.Bounds获取逻辑分辨率(即显示设置里的“缩放后尺寸”),而不是硬写1920x1080 - 若需精确到像素级(如自动化测试),改用
Graphics.CopyFromScreen的重载版本,传入Point(0,0)和目标Size,并确保目标Bitmap已按缩放比预分配——例如缩放 125%,则实际创建new Bitmap(1920 * 1.25, 1080 * 1.25) - 避免在 WPF 或 .NET Core/.NET 6+ WinForms 应用中直接用
Graphics.CopyFromScreen而不处理 DPI:它在无PerMonitorDPIAware=true清单时行为不稳定
截取指定窗口区域要用 GetWindowRect + Graphics.CopyFromScreen
Graphics.CopyFromScreen 只能从屏幕坐标抓图,不能直接“截某个窗体”。要截特定窗口(比如句柄为 hwnd 的 Notepad),得先拿到它的屏幕坐标矩形。
实操建议:
- 用 P/Invoke 调用
GetWindowRect(来自user32.dll),传入窗口句柄,获取RECT结构体,再转成Rectangle - 注意:
GetWindowRect返回的是包含边框和标题栏的完整区域;若只要客户区(Client Area),得再调GetClientRect+ClientToScreen组合换算 - 别用
Control.Bounds或Form.DesktopBounds替代——它们返回的是逻辑坐标,且在多显示器、缩放不一致时极易错位 - 示例关键行:
GetWindowRect(hwnd, out RECT rect);<br>var bounds = Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);<br>graphics.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
Graphics.CopyFromScreen 在 .NET 5+ 中仍可用,但推荐迁移到 Graphics.CopyFromScreen 的替代方案?不,别迁
有人看到文档里说 “GDI+ is deprecated”,就以为 Graphics.CopyFromScreen 也要淘汰。其实不是:它是 GDI+ 封装,但底层调用的是 Windows GDI 的 BitBlt,至今没被标记为 obsolete,.NET 6/7/8 全支持。
真正该警惕的是以下场景:
- 在非 UI 线程(如
Task.Run)中调用 ——Graphics.CopyFromScreen必须在 STA 线程且有消息泵(典型如 WinForms 主线程),否则抛InvalidOperationException:“No device context is associated with this Graphics object.” - 截图后立刻保存为 PNG:务必用
ImageFormat.Png,别用ImageFormat.Jpeg——JPG 会丢 Alpha 通道(哪怕你没透明内容,GDI+ 内部仍可能误判) - 频繁截图(如录屏)时,反复 new
Bitmap+Graphics会触发 GC 压力;应复用Bitmap实例,并在循环外创建Graphics对象
区域截图保存为文件时,路径权限和编码容易被忽略
截图代码跑在用户上下文,但保存路径若写死为 C:\screenshot.png,在非管理员账户下会因权限拒绝而静默失败(UnauthorizedAccessException 不一定抛出,有时只生成 0 字节文件)。
实操建议:
- 默认保存到
Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),这是用户有写权限的稳定路径 - 文件名含时间戳时,避免用
:(Windows 路径非法),改用DateTime.Now.ToString("yyyyMMdd_HHmmss") - 保存前加
Directory.CreateDirectory(Path.GetDirectoryName(filePath)),防止父目录不存在导致异常 - 别用
bitmap.Save(filePath)不带格式参数——它依赖文件扩展名推断格式,但某些扩展名(如.jpg)和实际编码器不匹配时会崩溃
截图逻辑本身不复杂,难的是在不同 DPI、多显示器、UAC 权限、后台线程这些真实环境里不出错。最常被跳过的一步是:每次创建 Bitmap 后,是否确认了它的 Width 和 Height 真的等于你预期的像素尺寸?打印出来看看,比猜快得多。


















