使用C#自制一个截屏工具
概述
在Windows Forms应用开发中,实现一个灵活、好用的屏幕截图功能,是很多开发者都会遇到的需求。今天要介绍的ScreenCapture辅助类,正是为此而生。它封装了一套完整的全屏区域截图逻辑:启动后,屏幕会覆盖一层半透明的黑色遮罩,用户只需按住鼠标左键并拖动,就能框选出任意矩形区域,松开鼠标即可完成截取。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

这个类的设计非常贴合实际场景,通常与一个PictureBox控件配合使用。用户点击“截图”按钮后,快速选取屏幕区域,截取的图像能立刻加载到图片框中,流程一气呵成。
主要功能
简单来说,这个类提供了以下核心能力:
- 直观的交互体验:全屏半透明遮罩,鼠标拖拽时,选中的区域会被高亮显示,操作感清晰。
- 便捷的取消操作:任何时候,按下ESC键就能立刻退出截图模式,不会产生任何结果。
- 灵活的返回值:直接返回标准的
Bitmap对象,你可以轻松地将其转换为OpenCvSharp.Mat或其他任何需要的图像格式,方便后续处理。 - 规范的资源管理:类实现了
IDisposable接口,建议配合using语句使用,确保内部资源(如覆盖窗体)能被及时释放。
使用方法
1. 在项目中添加文件
第一步很简单,直接将ScreenCapture.cs源代码文件添加到你的WinForms项目里即可。
2. 基本调用示例
最基础的用法,几行代码就能搞定。下面这段代码展示了如何截图并转换为OpenCV的Mat格式:
using (var screenCapture = new ScreenCapture())
{
Bitmap capturedBmp = screenCapture.CaptureScreen();
if (capturedBmp != null)
{
// 将 Bitmap 转换为 OpenCvSharp.Mat(需引用 OpenCvSharp.Extensions)
Mat mat = BitmapConverter.ToMat(capturedBmp);
// 显示到 PictureBox
pictureBox1.Image?.Dispose();
pictureBox1.Image = mat.ToBitmap();
}
}
3. 配合按钮点击事件使用(标准用法)
在实际应用中,截图功能通常由一个按钮触发。这里有个关键细节:为了截取到“干净”的屏幕(不包含自己的主窗体),通常需要先将主窗体隐藏起来。
private void btnScreenshot_Click(object sender, EventArgs e)
{
// 隐藏当前窗体,避免遮挡截图界面
this.Hide();
// 等待窗体完全隐藏,200毫秒通常足够
System.Threading.Thread.Sleep(200);
using (var cap = new ScreenCapture())
{
var bmp = cap.CaptureScreen();
if (bmp != null)
{
// 处理截图结果,例如显示在 PictureBox 中
pictureBox1.Image?.Dispose();
pictureBox1.Image = bmp;
}
}
// 重新显示主窗体
this.Show();
}
4. 其他示例
如果你的处理逻辑更复杂,或者需要复用截图代码,可以将其封装成一个带回调的方法。下面的例子展示了如何将截图用于OCR场景:
private void btnScreenshotOcr_Click(object sender, EventArgs e)
{
TakeScreenshot(img =>
{
currentOcrImage?.Dispose();
currentOcrImage = img.Clone();
ShowImage(pictureBoxOcr, img);
});
}
private void TakeScreenshot(Action onCaptured)
{
this.Hide();
System.Threading.Thread.Sleep(200);
using (var cap = new ScreenCapture())
{
var bmp = cap.CaptureScreen();
if (bmp != null)
{
Mat mat = OpenCvSharp.Extensions.BitmapConverter.ToMat(bmp);
onCaptured?.Invoke(mat);
}
}
this.Show();
}
注意事项
截图期间主窗体隐藏
正如示例所示,为了获得纯净的截图背景,通常需要将主窗体隐藏(this.Hide()),截图完成后再显示(this.Show())。这里有个小技巧:隐藏后最好等待一小段时间(比如200毫秒),确保窗体已经完全从屏幕上移除,再开始截图,这样更稳妥。
屏幕 DPI 缩放
在高DPI环境下,Graphics.CopyFromScreen方法会按物理屏幕坐标进行截取,所以通常情况下没有问题。如果你的应用对缩放比例有特殊要求,可能需要在此基础上做进一步的坐标转换和调整。
取消截图
用户按下ESC键后,内部窗体的DialogResult会返回Cancel,导致CaptureScreen()方法返回null。因此,你的代码里一定要记得检查返回值是否为null,并做相应的处理。
线程安全
需要特别注意,ScreenCapture内部使用了ShowDialog()来显示模态的覆盖层窗体,这意味着它必须在UI线程中调用。千万不要在后台线程里直接使用它,否则会引发跨线程操作控件的异常。
资源释放
这个类实现了IDisposable接口,内部会管理一些窗体资源。最佳实践是使用using语句来包裹它,这样即使发生异常,资源也能被正确释放。当然,手动调用Dispose()也是可以的。
内部结构说明
了解其内部构造,有助于你更灵活地使用或修改它。这个类主要包含两部分:
SelectionOverlay:这是一个继承自Form的内部类,是整个功能的核心。它负责显示全屏半透明遮罩,并处理所有鼠标拖拽和键盘事件(比如ESC键)。SelectedRegion属性:它记录了用户最终选中的矩形区域,坐标是基于整个屏幕的。
截图的“魔法”发生在CaptureScreen方法里:当用户确认选区后,它会利用Graphics.CopyFromScreen方法,将屏幕上对应矩形区域的像素数据,原封不动地复制到一个新的Bitmap对象中。
依赖项
要使用这个类,你的项目需要引用以下基础库:
System.DrawingSystem.Windows.Forms
如果你需要将截图结果转换为OpenCV的Mat格式进行图像处理,那么还需要额外引用OpenCvSharp.Extensions(这是一个可选依赖)。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
///
/// 屏幕截图辅助类,提供全屏区域截图功能。
/// 使用方法:
///
/// using (var cap = new ScreenCapture())
/// {
/// Bitmap bmp = cap.CaptureScreen();
/// if (bmp != null)
/// {
/// // 处理截图
/// }
/// }
///
///
public class ScreenCapture : IDisposable
{
///
/// 启动全屏选区截图,返回用户选中的区域图像。
///
/// 截取到的 Bitmap 图像;如果用户取消操作或选区无效,返回 null。
public Bitmap CaptureScreen()
{
// 创建并显示选区覆盖层窗体(模态对话框)
using (var overlay = new SelectionOverlay())
{
// 显示对话框,等待用户操作
var result = overlay.ShowDialog();
// 用户确认且区有效
if (result == DialogResult.OK && overlay.SelectedRegion != Rectangle.Empty)
{
Rectangle bounds = overlay.SelectedRegion;
// 创建与选区相同尺寸的 Bitmap
Bitmap bmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bmp))
{
// 从屏幕复制选区内容到 Bitmap
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
}
return bmp;
}
}
return null;
}
///
/// 全屏选区覆盖层窗体(内部类),提供半透明背景和鼠标拖拽选择功能。
///
private class SelectionOverlay : Form
{
/// 用户最终选中的屏幕区域(屏幕坐标)。
public Rectangle SelectedRegion { get; private set; } = Rectangle.Empty;
private Point startPoint; // 鼠标按下时的起始点
private bool selecting = false; // 是否正在拖拽选择中
private Rectangle currentRect; // 当前拖拽的矩形
private Pen selectionPen; // 绘制选择框的画笔
///
/// 初始化覆盖层窗体。
///
public SelectionOverlay()
{
// 无边框、最大化填满屏幕
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Maximized;
// 黑色半透明背景,实现“遮罩”效果
this.BackColor = Color.Black;
this.Opacity = 0.6; // 透明度 0.6,突出选框区域
this.DoubleBuffered = true; // 减少闪烁
this.TopMost = true; // 置顶,覆盖所有窗口
this.Cursor = Cursors.Cross; // 十字光标,适合选区操作
this.KeyPreview = true; // 让窗体优先接收键盘事件(如 ESC)
// 初始化画笔:半透明绿色,2像素宽
selectionPen = new Pen(Color.FromArgb(100, 0, 255, 0), 2);
// 绑定事件
this.MouseDown += OnMouseDown;
this.MouseMove += OnMouseMove;
this.MouseUp += OnMouseUp;
this.Paint += OnPaint;
this.KeyDown += OnKeyDown;
}
///
/// 鼠标按下:开始选区。
///
private void OnMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
startPoint = e.Location; // 记录起始点
selecting = true; // 进入选择模式
currentRect = new Rectangle(startPoint, new Size(0, 0)); // 初始矩形为空
Invalidate(); // 触发重绘
}
}
///
/// 鼠标移动:更新当前选区矩形并重绘。
///
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (selecting)
{
// 计算矩形的正确边界(支持向左/向上拖拽)
int x = Math.Min(startPoint.X, e.X);
int y = Math.Min(startPoint.Y, e.Y);
int w = Math.Abs(startPoint.X - e.X);
int h = Math.Abs(startPoint.Y - e.Y);
currentRect = new Rectangle(x, y, w, h);
Invalidate(); // 触发 OnPaint 重绘
}
}
///
/// 鼠标释放:完成选区。
///
private void OnMouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && selecting && currentRect.Width > 5 && currentRect.Height > 5)
{
// 选区宽度和高度至少为5像素,避免误触
SelectedRegion = currentRect; // 保存选区
this.DialogResult = DialogResult.OK; // 设置对话框结果为 OK
this.Close(); // 关闭覆盖层
}
selecting = false; // 退出选择模式
}
///
/// 绘制覆盖层内容:在选区边缘绘制矩形框。
///
private void OnPaint(object sender, PaintEventArgs e)
{
if (selecting && currentRect.Width > 0 && currentRect.Height > 0)
{
// 绘制矩形框(仅边框,不填充)
e.Graphics.DrawRectangle(selectionPen, currentRect);
}
}
///
/// 键盘按下:按 ESC 键取消截图。
///
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
{
this.DialogResult = DialogResult.Cancel; // 取消操作
this.Close();
}
}
///
/// 释放资源。
///
protected override void Dispose(bool disposing)
{
if (disposing)
{
selectionPen?.Dispose(); // 释放画笔
}
base.Dispose(disposing);
}
}
///
/// 实现 IDisposable 接口(当前类无额外需要释放的资源,但保留方法以备将来扩展)。
///
public void Dispose()
{
// 无托管资源需要释放,但为了接口完整性保留空方法
}
}
} 热门专题
热门推荐
我国刀具市场发展调研报告 在当今制造业持续升级的背景下,市场调研报告的重要性日益凸显。一份结构清晰、数据翔实的报告,能为决策提供关键参考。以下这份关于我国刀具市场的调研报告,旨在梳理现状、剖析问题,并为未来发展提供借鉴。 当前,国内刀具年销售额约为145亿元,其中硬质合金刀具占比不足25%。这一比例
国内首份空净市场调研报告 在公众健康意识日益增强的今天,市场报告的重要性不言而喻。一份结构清晰、数据翔实的报告,能为行业描绘出精准的航图。那么,一份优秀的市场调研报告究竟该如何呈现?近期发布的这份国内空气净化器行业蓝皮书,或许能提供一个范本。 市场增长的势头有多强劲?数据显示,国内空气净化器市场正驶
水利工程供水管理调研报告 在各类报告日益成为工作常态的今天,撰写一份扎实的调研报告,关键在于厘清现状、找准问题、提出思路。这份关于水利工程供水管理的报告,旨在系统梳理情况,为后续决策提供参考。 一、基本情况 横跨区域的**水库及八座枢纽拦河闸,构成了**运河流域防洪与兴利供水的骨干工程体系。自投入运
财产保全申请书范本 一份规范的财产保全申请书,是启动财产保全程序的关键文书。其核心在于清晰、准确地列明各方信息、诉求与依据。通常,申请书的结构是固定的,但具体内容需要根据案件事实来填充。下面,我们通过几个典型的范本来拆解其中的要点。 篇一:通用格式范本 首先来看一个通用模板。这个模板清晰地勾勒出了申
“防台抗台”活动由学院的积极分子组成,他们踊跃报名,利用暑期时间奉献自己的青春,为社会尽一份力量。 带队的学院分团委书记吕老师点出了活动的深层价值:这不仅是一次能力锻炼,更是学生认识社会、融入社会并最终回馈社会的关键一步。经过这番历练,团队友谊愈发坚固,协作精神显著增强,感恩之心也油然而生。 青春洋





