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

概述

ScreenCapture 是一个辅助类,用于在 Windows Forms 应用程序中实现全屏区域截图功能。它提供了一个半透明覆盖层,用户可以按下鼠标左键并拖动选择一个矩形区域,松开鼠标后即可截取该区域的图像。

该类的设计初衷是配合 PictureBox 控件,让用户通过"截图"按钮快速截取屏幕任意区域,并自动加载到图片展示控件中。

主要功能

  • 全屏半透明遮罩,高亮显示鼠标拖拽的选区

  • 支持 ESC 键取消截图

  • 返回截图的 Bitmap 对象,可进一步转换为 OpenCvSharp.Mat 或其他图像格式

  • 实现了 IDisposable 接口,便于资源管理

使用方法

1. 在项目中添加文件

ScreenCapture.cs 添加到WinForms 项目中。

2. 基本调用示例

csharp

复制代码
// 实例化并调用截图
cs 复制代码
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. 配合按钮点击事件使用(标准用法)
cs 复制代码
private void btnScreenshot_Click(object sender, EventArgs e)
{
    // 隐藏当前窗体,避免遮挡截图界面
    this.Hide();
    // 等待窗体完全隐藏
    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. 其他示例
cs 复制代码
private void btnScreenshotOcr_Click(object sender, EventArgs e)
{
    TakeScreenshot(img =>
    {
        currentOcrImage?.Dispose();
        currentOcrImage = img.Clone();
        ShowImage(pictureBoxOcr, img);
    });
}

private void TakeScreenshot(Action<Mat> 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();
}

注意事项

  1. 截图期间主窗体隐藏

    为了获得纯净的截图背景,通常需要将主窗体隐藏(this.Hide()),截图完成后再显示(this.Show())。注意等待一小段时间(如 200ms)确保窗体完全隐藏。

  2. 屏幕 DPI 缩放

    在高 DPI 环境下,Graphics.CopyFromScreen 会按物理屏幕坐标截取,通常没有问题。如果需要考虑缩放比例,可以进一步调整。

  3. 取消截图

    用户按下 ESC 键后,DialogResult 会返回 CancelCaptureScreen() 返回 null。您的代码应当处理 null 情况。

  4. 线程安全
    ScreenCapture 内部使用 ShowDialog() 显示模态覆盖层,必须在 UI 线程调用。不要在后台线程中直接调用。

  5. 资源释放

    类实现了 IDisposable,务必使用 using 语句或手动调用 Dispose() 释放内部资源(如覆盖层窗体)。

内部结构说明

  • SelectionOverlay 是一个继承自 Form 的内部类,负责显示半透明全屏遮罩,处理鼠标拖拽和键盘事件。

  • SelectedRegion 属性记录了用户选中的矩形区域(屏幕坐标)。

  • 截图操作通过 Graphics.CopyFromScreen 将选中区域复制到 Bitmap 中。

依赖项

  • 需要引用 System.DrawingSystem.Windows.Forms

  • 如需转换为 Mat,还需要 OpenCvSharp.Extensions(可选)。

///ScreenCapture.cs

cs 复制代码
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    /// <summary>
    /// 屏幕截图辅助类,提供全屏区域截图功能。
    /// 使用方法:
    /// <code>
    /// using (var cap = new ScreenCapture())
    /// {
    ///     Bitmap bmp = cap.CaptureScreen();
    ///     if (bmp != null)
    ///     {
    ///         // 处理截图
    ///     }
    /// }
    /// </code>
    /// </summary>
    public class ScreenCapture : IDisposable
    {
        /// <summary>
        /// 启动全屏选区截图,返回用户选中的区域图像。
        /// </summary>
        /// <returns>截取到的 Bitmap 图像;如果用户取消操作或选区无效,返回 null。</returns>
        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;
        }

        /// <summary>
        /// 全屏选区覆盖层窗体(内部类),提供半透明背景和鼠标拖拽选择功能。
        /// </summary>
        private class SelectionOverlay : Form
        {
            /// <summary>用户最终选中的屏幕区域(屏幕坐标)。</summary>
            public Rectangle SelectedRegion { get; private set; } = Rectangle.Empty;

            private Point startPoint;          // 鼠标按下时的起始点
            private bool selecting = false;     // 是否正在拖拽选择中
            private Rectangle currentRect;      // 当前拖拽的矩形
            private Pen selectionPen;           // 绘制选择框的画笔

            /// <summary>
            /// 初始化覆盖层窗体。
            /// </summary>
            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;
            }

            /// <summary>
            /// 鼠标按下:开始选区。
            /// </summary>
            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();                      // 触发重绘
                }
            }

            /// <summary>
            /// 鼠标移动:更新当前选区矩形并重绘。
            /// </summary>
            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 重绘
                }
            }

            /// <summary>
            /// 鼠标释放:完成选区。
            /// </summary>
            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;  // 退出选择模式
            }

            /// <summary>
            /// 绘制覆盖层内容:在选区边缘绘制矩形框。
            /// </summary>
            private void OnPaint(object sender, PaintEventArgs e)
            {
                if (selecting && currentRect.Width > 0 && currentRect.Height > 0)
                {
                    // 绘制矩形框(仅边框,不填充)
                    e.Graphics.DrawRectangle(selectionPen, currentRect);
                }
            }

            /// <summary>
            /// 键盘按下:按 ESC 键取消截图。
            /// </summary>
            private void OnKeyDown(object sender, KeyEventArgs e)
            {
                if (e.KeyCode == Keys.Escape)
                {
                    this.DialogResult = DialogResult.Cancel;  // 取消操作
                    this.Close();
                }
            }

            /// <summary>
            /// 释放资源。
            /// </summary>
            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    selectionPen?.Dispose();  // 释放画笔
                }
                base.Dispose(disposing);
            }
        }

        /// <summary>
        /// 实现 IDisposable 接口(当前类无额外需要释放的资源,但保留方法以备将来扩展)。
        /// </summary>
        public void Dispose()
        {
            // 无托管资源需要释放,但为了接口完整性保留空方法
        }
    }
}
相关推荐
少控科技4 小时前
小数典应用:小诗典
windows·c#
wuyoula5 小时前
尹之盾企业版网络验证
服务器·开发语言·javascript·c++·人工智能·ui·c#
zdr尽职尽责6 小时前
Untiy 处理Aseprite 资产 解决偏移问题
学习·unity·c#·游戏引擎
步步为营DotNet6 小时前
.NET 11 与 C# 14 助力云原生应用安全架构升级
云原生·c#·.net
少控科技6 小时前
小数典应用:农场环境数据采集监控
开发语言·windows·c#
¥-oriented7 小时前
记录使用C#编程中遇到的一个小bug
c#·bug
唐青枫8 小时前
C#.NET MemoryMarshal 深入解析:零拷贝内存重解释、二进制读写与使用边界
c#·.net
成都易yisdong1 天前
纬地、鸿业、海地、CASS等横断面数据互转工具V3.2——测绘与道路设计人员的效率神器
c#·visual studio code
AIKZX1 天前
西门子博途 TIA Portal v18 中文版图文安装教程(超级详细)附下载链接
开发语言·c#·编辑器·idea