游乐游手机版
首页/编程语言/文章详情

使用C#自制一个截屏工具

时间:2026-04-27 19:14
概述 在Windows Forms应用开发中,实现一个灵活、好用的屏幕截图功能,是很多开发者都会遇到的需求。今天要介绍的ScreenCapture辅助类,正是为此而生。它封装了一套完整的全屏区域截图逻辑:启动后,屏幕会覆盖一层半透明的黑色遮罩,用户只需按住鼠标左键并拖动,就能框选出任意矩形区域,松开

概述

在Windows Forms应用开发中,实现一个灵活、好用的屏幕截图功能,是很多开发者都会遇到的需求。今天要介绍的ScreenCapture辅助类,正是为此而生。它封装了一套完整的全屏区域截图逻辑:启动后,屏幕会覆盖一层半透明的黑色遮罩,用户只需按住鼠标左键并拖动,就能框选出任意矩形区域,松开鼠标即可完成截取。

使用C#自制一个截屏工具

这个类的设计非常贴合实际场景,通常与一个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.Drawing
  • System.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()
        {
            // 无托管资源需要释放,但为了接口完整性保留空方法
        }
    }
}
来源:https://www.jb51.net/program/362911d5t.htm
上一篇面试必问的Java运行时架构JVM详解 下一篇GOLang判断进程是否存在实现方式
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
CentOS与Golang打包常见兼容性问题探讨
编程语言 · 2026-07-01

CentOS与Golang打包常见兼容性问题探讨

CentOS与Golang打包的兼容性问题集中在glibc版本不匹配、交叉编译环境变量错误、依赖库缺失及Go依赖管理不规范。可通过Docker容器编译、选择兼容Go版本、正确设置GOOS GOARCH环境变量、安装对应开发包及使用GoModules解决。

CentOS中Fortran与Python如何协同工作从入门到实战完整教程
编程语言 · 2026-07-01

CentOS中Fortran与Python如何协同工作从入门到实战完整教程

在CentOS中,Fortran与Python可通过f2py、SWIG、共享库调用或subprocess协同。f2py封装Fortran为Python模块,支持数组运算;共享库需手动对齐数据类型;系统调用适合独立计算。

CentOS中Golang打包优化方法
编程语言 · 2026-07-01

CentOS中Golang打包优化方法

在CentOS中优化Golang编译打包,可显著提升编译速度并减小二进制文件体积。关键技巧包括:设置环境变量、使用Go模块管理依赖、编译时添加-ldflags= "-s-w "去除调试信息、利用UPX工具压缩、运行strip清理符号表,以及优化cgo内C代码的编译选项。综合运用这些方法能有效优化最终程序。

在CentOS系统中cpustat与其他工具协同使用的完整方法
编程语言 · 2026-07-01

在CentOS系统中cpustat与其他工具协同使用的完整方法

cpustat作为sysstat包的CPU监控工具,可通过管道与grep等命令配合过滤数据,利用脚本自动记录带时间戳的日志,或结合图形工具查看,也可格式化输出后接入Zabbix、Grafana等Web监控系统,实现可视化与告警。

CentOS中readdir与其他Linux发行版的差异
编程语言 · 2026-07-01

CentOS中readdir与其他Linux发行版的差异

CentOS基于RHEL,与Ubuntu、Debian、Fedora在包管理器(yum dnfvsapt)、默认文件系统(XFSvsext4)等存在差异,但readdir等系统调用遵循POSIX标准,行为一致。