C#系统桌面截图功能实现(三)

345 阅读4分钟
  • 摘要: 本文描述了如何使用C# 实现WinForms桌面截图程序。

  • 前言

    • 此程序实现了三种截图模式:全屏截图、工作区截图和区域选择截图,并包含简单的图片保存功能。核心内容包括:屏幕区域选择、DPI感知处理、实时尺寸显示、图片保存等。
    • 关键技术点:

      • 屏幕捕获:通过Graphics.CopyFromScreen方法实现屏幕内容捕获。
      • DPI感知处理:使用SetProcessDPIAware API应对高DPI屏幕环境。
      • 透明窗体:通过设置窗体透明度实现区域选择界面。
      • 鼠标交互处理:重写鼠标事件实现区域选择和实时反馈。
      • 图像处理:支持截图后的图片保存功能。
    • 核心内容:

      • 主窗体(MainForm)。
      • 截图窗体(FrmScreenshot)。
      • 编辑窗体(FrmScreenshotEditor)。
      • 其中截图窗体实现了最核心的区域选择功能,通过半透明覆盖层和实时矩形绘制提供了直观的用户体验
    • 截图模式选择:

      • 全屏截图:捕获整个屏幕内容。
      • 工作区截图:捕获不包括任务栏的工作区域。
      • 区域截图:通过鼠标拖拽选择任意矩形区域。
      • 在区域截图模式下,界面显示半透明黑色覆盖层,用户选择的区域会高亮显示,并实时显示选择区域的尺寸。截图完成后可进入编辑界面查看和保存图片。
      • 此程序实现了桌面截图工具的核心功能,即使在高DPI环境下也有较好的用户交互体验,程序代码结构清晰。

  • 运行环境

    • 编程语言: C#
    • 开发平台:.NET Framework 4.8.0
    • 开发工具: Visual Studio 2022
    • 操作系统: Windows 10
  • 运行效果

    • 主窗体

    image.png

    • 区域截图

image.png


  • 代码

    • 主窗体代码

      • 主窗体中添加三个截图按钮,根据不同按钮实现不同的截图功能,在截图前隐藏该主窗体,否则会截到主窗体,添加一定的延时,否则还是会截到主窗体,因为主窗体可能未隐藏成功。截图成功后再显示子窗体和主窗体。
      public partial class MainForm : Form
      {
          FrmScreenshotEditor frmScreenshotEditor = new FrmScreenshotEditor();
          public MainForm()
          {
              InitializeComponent();
              this.CenterToParent();
              this.Location = new Point(100,100);
              this.MaximumSize = this.Size;
              this.MinimumSize = this.Size;
          }
          private void Btn_FullScreen_Click(object sender, EventArgs e)
          {
              Screenshot(frmScreenshotEditor,1);
          }
          private void Btn_WorkingArea_Click(object sender, EventArgs e)
          {
              Screenshot(frmScreenshotEditor, 2);
          }
          private void Btn_ScreenArea_Click(object sender, EventArgs e)
          {
              Screenshot(frmScreenshotEditor);
          }
      
          private void Screenshot(FrmScreenshotEditor form, int mode = 0)
          {
              this.Hide();
              form = new FrmScreenshotEditor();
              FrmScreenshot frmScreenshot = new FrmScreenshot(form, mode);
              if (mode == 0)
              {
                  frmScreenshot.ShowDialog();
              }
              if (form.Image == null)
              {
                  this.Show();
                  return;
              }
              else
              {
                  Image image = new Bitmap(form.Image);
                  form.PixelSize = image.Size;
                  Debug.WriteLine($"图像大小:X = {image.Width},Y = {image.Height}");
                  form.ShowDialog();
                  this.Show();
              }
          }
      }
      
    • 截图窗体代码

      • 声明DPI感知方法,适应系统缩放显示。实现了基本的全屏、工作区、区域截图等功能。区域截图时显示桌面鼠标定及截图大小功能。
      #region 方法|自定义方法
       /// <summary>
       /// DPI感知声明:启动时调用,应对屏幕缩放
       /// </summary>
       [System.Runtime.InteropServices.DllImport("user32.dll")]
       private static extern bool SetProcessDPIAware();
       /// <summary>
       /// 截屏方式
       /// </summary>
       private void ScreenshotMethod(int mode)
       {
           switch (mode)
           {
               case 0:
                   frm_ScreenshotEidtor.Image = CaptureSelectedRegion();
                   break;
               case 1:
                   Thread.Sleep(300);
                   frm_ScreenshotEidtor.Image = CaptureFullScreen();
                   this.Close();
                   break;
               case 2:
                   Thread.Sleep(300);
                   frm_ScreenshotEidtor.Image = CaptureWorkingArea();
                   this.Close();
                   break;
               default:
                   frm_ScreenshotEidtor.Image = CaptureSelectedRegion();
                   break;
           }
       }
       //采集选择区域
       public Bitmap CaptureSelectedRegion()
       {
           // 计算选择区域
           int captureX = Math.Min(startPoint.X, endPoint.X);
           int captureY = Math.Min(startPoint.Y, endPoint.Y);
           int captureWidth = Math.Abs(startPoint.X - endPoint.X);
           int captureHeight = Math.Abs(startPoint.Y - endPoint.Y);
           if (captureWidth <= 0 || captureHeight <= 0) return null;
      
           // 获取屏幕DPI缩放比例
           float dpiScaleX, dpiScaleY;
           using (Graphics g = this.CreateGraphics())
           {
               dpiScaleX = g.DpiX / 96f;
               dpiScaleY = g.DpiY / 96f;
           }
           // 将逻辑坐标转换为物理坐标
           Point upperLeftSource = this.PointToScreen(new Point(
               (int)(captureX * dpiScaleX),
               (int)(captureY * dpiScaleY)));
           // 计算物理尺寸
           int physicalWidth = (int)(captureWidth * dpiScaleX);
           int physicalHeight = (int)(captureHeight * dpiScaleY);
           this.Invalidate();
           Debug.WriteLine($"起始位置:{startPoint},结束位置:{endPoint}");
           Debug.WriteLine($"采集X:{captureX},采集Y:{captureY}");
           Debug.WriteLine($"采集宽度:{captureWidth},采集高度:{captureHeight}");
           Debug.WriteLine($"转换点:{upperLeftSource}");
      
           // 截取屏幕
           Bitmap bitmap = new Bitmap(captureWidth, captureHeight);
           using (Graphics g = Graphics.FromImage(bitmap))
           {
               g.CopyFromScreen(upperLeftSource, Point.Empty, new Size(captureWidth, captureHeight));
           }
           return bitmap;
       }
       /// <summary>
       /// 截取全屏:
       /// </summary>
       public Bitmap CaptureFullScreen()
       {
           // 获取主屏幕的工作区域(不包含任务栏)
           Rectangle bounds = Screen.GetBounds(Point.Empty);
           // 获取屏幕DPI缩放比例
           float dpiScaleX, dpiScaleY;
           using (Graphics g = this.CreateGraphics())
           {
               dpiScaleX = g.DpiX / 96f;
               dpiScaleY = g.DpiY / 96f;
           }
           // 计算物理尺寸
           int physicalWidth = (int)(bounds.Width * dpiScaleX);
           int physicalHeight = (int)(bounds.Height * dpiScaleY);
      
           Bitmap bitmap = new Bitmap(physicalWidth, physicalHeight);
           using (Graphics g = Graphics.FromImage(bitmap))
           {
               g.CopyFromScreen(new Point(0,0), Point.Empty, new Size(physicalWidth, physicalHeight));
           }
           return bitmap;
       }
       /// <summary>
       /// 截取工作区:
       /// </summary>
       public Bitmap CaptureWorkingArea()
       {
           // 获取主屏幕的工作区域(不包含任务栏)
           Rectangle bounds = Screen.PrimaryScreen.WorkingArea;
           // 获取屏幕DPI缩放比例
           float dpiScaleX, dpiScaleY;
           using (Graphics g = this.CreateGraphics())
           {
               dpiScaleX = g.DpiX / 96f;
               dpiScaleY = g.DpiY / 96f;
           }
           // 计算物理尺寸
           int physicalWidth = (int)(bounds.Width * dpiScaleX);
           int physicalHeight = (int)(bounds.Height * dpiScaleY);
      
           Bitmap bitmap = new Bitmap(physicalWidth, physicalHeight);
           using (Graphics g = Graphics.FromImage(bitmap))
           {
               g.CopyFromScreen(new Point(0, 0), Point.Empty, new Size(physicalWidth, physicalHeight));
           }
           return bitmap;
       }
       /// <summary>
       /// 显示文本:鼠标位置
       /// </summary>
       private void DrawMousePosition(Graphics graphics)
       {
           using (Pen pen = new Pen(Color.Red, 1))
           {
               int x = Math.Min(startPoint.X, endPoint.X);
               int y = Math.Min(startPoint.Y, endPoint.Y);
               int width = Math.Abs(startPoint.X - endPoint.X);
               int height = Math.Abs(startPoint.Y - endPoint.Y);
               // 绘制选择矩形
               DrawSelectedRegion(graphics, new Rectangle(x, y, width, height));
               // 显示尺寸信息
               string sizeText = $"{width} x {height}";
               Font font = new Font("Arial", 14);
               SizeF textSize = graphics.MeasureString(sizeText, font);
               // 确保文本不会超出屏幕
               float textX = x + width - textSize.Width - 5;
               float textY = y + height - textSize.Height - 5;
               if (textX < x) textX = x + 5;
               if (textY < y) textY = y + 5;
               // 绘制文本背景
               graphics.FillRectangle(Brushes.White, textX - 2, textY - 2, textSize.Width + 4, textSize.Height + 4);
               // 绘制文本
               graphics.DrawString(sizeText, font, Brushes.Blue, textX, textY);
           }
       }
       /// <summary>
       /// 绘制选择矩形
       /// </summary>
       private void DrawSelectedRegion(Graphics graphics, Rectangle selectionRect)
       {
           // 1、绘制半透明背景
           using (Brush overlayBrush = new SolidBrush(Color.FromArgb(128, Color.Black)))
           {
               graphics.FillRectangle(overlayBrush, this.ClientRectangle);
           }
           graphics.SetClip(selectionRect, CombineMode.Exclude);
           graphics.Clear(Color.FromArgb(255, 0, 0, 0));
           graphics.ResetClip();
           // 2. 如果有选择区域,使该区域完全透明
           using (Pen pen = new Pen(Color.Blue, 1))
           {
               graphics.DrawRectangle(pen, selectionRect);
           }
       }
       #endregion
      
  • 截图编辑窗体代码

    • 在截图编辑子窗体中添加自定义图像显示控件,该控件需要实现图像的滚动和缩放功能。添加一个Image属性,用于设置显示的图片。最后添加一个保存按钮,实现图像保存功能。
      public partial class FrmScreenshotEditor :WinFormBase
       {
           public Image Image
           {
               get => uvCanvas.Image;
               set
               {
                   uvCanvas.Image = value;
                   Invalidate();
               }
           }
           public FrmScreenshotEditor()
           {
               InitializeComponent();
               this.CenterToParent();
               this.WindowState = FormWindowState.Maximized;
           }
           private void btn_Save_Click(object sender, System.EventArgs e)
           {
               SaveFileDialog  saveFileDialog = new SaveFileDialog();
               saveFileDialog.FileName = "截图1";     //设置初始文件名
               saveFileDialog.Filter= "PNG Image|*.png|JPEG Image|*.jpg|BMP Image|*.bmp";
               if (saveFileDialog.ShowDialog()== DialogResult.OK)
               {
                   string extension = Path.GetExtension(saveFileDialog.FileName).ToLower();
                   ImageFormat imageFormat;
                   switch (extension)
                   {
                       case ".png":
                           imageFormat = ImageFormat.Png;
                           break;
                       case ".jpg":
                           imageFormat = ImageFormat.Jpeg;
                           break;
                       case ".bmp":
                           imageFormat = ImageFormat.Bmp;
                           break;
                       default:
                           imageFormat = ImageFormat.Png;
                           break;
                   }
                   try
                   {
                       Image.Save(saveFileDialog.FileName, imageFormat);
                       MessageBox.Show($"图片已成功保存至: {saveFileDialog.FileName}");
                   }
                   catch (Exception ex)
                   {
                       MessageBox.Show($"保存图片时出错: {ex.Message}");
                   }
               }
           }
       }
      

  • 总结

    • 本文详细介绍了如何使用C# WinForms桌面截图工具程序,主要内容包括:主窗体(MainForm)提供三种截图模式入口,协调各窗体间交互实现(全屏/工作区/区域)三种截图方式。
    • 创建方法用于调整DPI感知处理,确保高分辨率屏幕下的截取图像的准确性。编辑窗体(FrmScreenshotEditor)提供基本的图片查看和保存功能。
    • 该程序完整实现了桌面截图工具的核心需求,代码结构清晰,交互设计直观友好。可以在此基础上进一步扩展编辑功能,如添加标注、文字等,打造更完善的截图工具。
  • 最后

    • 项目源码:gitee.com/incodenotes…
    • 如果你觉得这篇文章对你有帮助,不妨点个赞再走呗!
    • 如有其他疑问,欢迎评论区留言讨论!
    • 也可以关注微信公众号 [编程笔记in] ,一起交流学习!