您的位置:首页 >使用C#自制一个截屏工具
发布于2026-04-27 阅读(0)
扫一扫,手机访问
在Windows Forms应用开发中,实现一个灵活、好用的屏幕截图功能,是很多开发者都会遇到的需求。今天要介绍的ScreenCapture辅助类,正是为此而生。它封装了一套完整的全屏区域截图逻辑:启动后,屏幕会覆盖一层半透明的黑色遮罩,用户只需按住鼠标左键并拖动,就能框选出任意矩形区域,松开鼠标即可完成截取。

这个类的设计非常贴合实际场景,通常与一个PictureBox控件配合使用。用户点击“截图”按钮后,快速选取屏幕区域,截取的图像能立刻加载到图片框中,流程一气呵成。
简单来说,这个类提供了以下核心能力:
Bitmap对象,你可以轻松地将其转换为OpenCvSharp.Mat或其他任何需要的图像格式,方便后续处理。IDisposable接口,建议配合using语句使用,确保内部资源(如覆盖窗体)能被及时释放。第一步很简单,直接将ScreenCapture.cs源代码文件添加到你的WinForms项目里即可。
最基础的用法,几行代码就能搞定。下面这段代码展示了如何截图并转换为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();
}
}
在实际应用中,截图功能通常由一个按钮触发。这里有个关键细节:为了截取到“干净”的屏幕(不包含自己的主窗体),通常需要先将主窗体隐藏起来。
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();
}
如果你的处理逻辑更复杂,或者需要复用截图代码,可以将其封装成一个带回调的方法。下面的例子展示了如何将截图用于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()
{
// 无托管资源需要释放,但为了接口完整性保留空方法
}
}
}
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9