Graphics.CopyFromScreen是Windows Forms下最轻量可控的局部截屏方案,需注意屏幕坐标系、目标Bitmap预创建、多屏适配及WPF应改用RenderTargetBitmap;保存时必须显式指定ImageFormat避免默认BMP。
用 Graphics.CopyFromScreen 截指定矩形区域最直接
windows forms 下截屏,graphics.copyfromscreen 是最轻量、最可控的方案,不需要额外依赖或权限弹窗。它本质是把屏幕某块像素“复制”到内存位图里,适合做局部截图工具的核心逻辑。
常见错误是传错坐标:屏幕坐标系原点在左上角,而 CopyFromScreen 的前两个参数是「屏幕左上角的 X/Y」,不是控件坐标或窗口客户区坐标。容易误用 this.Location 或 PointToScreen(new Point(0, 0)) 而没考虑窗体边框、标题栏高度。
实操建议:
- 先用
Screen.PrimaryScreen.Bounds确认主屏分辨率,避免跨屏时坐标越界 - 要截窗口内某个控件区域,用
control.PointToScreen(new Point(0, 0))获取其左上角屏幕坐标,再叠加control.Width和control.Height - 目标
Bitmap必须提前创建好,宽高严格匹配要截的区域尺寸,否则会拉伸或裁剪
var bounds = new Rectangle(100, 200, 800, 600); // 屏幕上 x=100,y=200 开始,宽800高600 using var bmp = new Bitmap(bounds.Width, bounds.Height); using var g = Graphics.FromImage(bmp); g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size); // 注意第二个参数是目标位图的左上角(通常是 Point.Empty)
WPF 里别硬套 Graphics,改用 RenderTargetBitmap + VisualBrush
WPF 渲染管线和 GDI 不兼容,强行在 WPF 窗口上调用 Graphics.CopyFromScreen 虽然能跑,但可能截不到硬件加速渲染的内容(比如 MediaElement 播放的视频、某些 WebView2 区域),而且坐标换算更易出错。
真正适配 WPF 的做法是:把要截的 Visual(比如一个 Grid 或整个 Window)用 RenderTargetBitmap 渲染成位图。这本质是“截图当前 WPF 视图树”,不依赖屏幕像素,也不受 DPI 缩放干扰。
关键注意点:
-
RenderTargetBitmap构造函数的前两个参数是「输出位图的像素宽高」,不是逻辑单位;需手动乘以VisualTreeHelper.GetDpi(visual).PixelsPerinchX / 96.0做 DPI 补偿,否则高清屏下会模糊 - 如果目标
Visual尚未加载(IsLoaded == false)或不可见(Visibility != Visible),渲染结果为空白 - 不能直接截
WebView2这类外部进程控件——它不在 WPF 渲染树里,得回退到CopyFromScreen并精确计算其屏幕位置
保存为 PNG 时别漏掉 ImageFormat.Png 参数
Bitmap.Save(path) 默认用 BMP 格式,文件体积大、无压缩,且不保留 Alpha 通道(哪怕你截的是带透明 UI)。用户点保存却得到几 MB 的 BMP,第一反应是“程序卡了”或“坏了”。
必须显式指定格式:
- 截图存档或二次编辑 → 用
ImageFormat.Png(无损、支持透明、体积合理) - 发给非技术人员看图 →
ImageFormat.Jpeg,但记得加Encoder.Quality参数设为 90 以上,否则文字边缘糊成一片 - 绝对不要用
Save(path)不带格式参数,也别依赖文件扩展名自动推断(.png后缀 ≠ PNG 编码)
bmp.Save(@"C:\cap.png", ImageFormat.Png); // 正确
多显示器环境下 Screen.AllScreens 比 PrimaryScreen 更可靠
用户连了双屏甚至三屏,截图区域横跨两个显示器,或者想让工具默认截「鼠标所在屏」,这时候只查 Screen.PrimaryScreen 就会出错——比如鼠标在副屏,你却从主屏坐标开始截,结果全是黑块或错位。
正确做法是根据鼠标位置反查屏幕:
- 用
Cursor.Position拿到当前鼠标屏幕坐标 - 遍历
Screen.AllScreens,用每个screen.Bounds.Contains(cursorPos)判断归属 - 再基于该
Screen的Bounds做相对坐标换算(比如想截鼠标周围 200×200 区域,就用new Rectangle(cursorPos.X - 100, cursorPos.Y - 100, 200, 200))
还有个隐藏坑:某些远程桌面或虚拟机环境里,Screen.AllScreens 可能返回空数组或单个假屏幕,得加空检查和 fallback。










