摘要: 本文描述如何使用C#实现Windows桌面截图功能,允许局部截图。
-
前言
-
通过案例学习是一种比较有趣的学习方式,下面是使用C#开发Windows桌面截图程序的案例。
-
案例实现了基本的屏幕截图功能,包括捕获全屏、预览图像以及保存截图文件。
-
通过该案例,可以让我们了解到使用GDI绘图的技术、文件对话框的使用以及事件驱动编程的概念。
-
通过自定义图像控件优化UI设计、实现图像显示,滚动缩放、显示图像信息等。
-
实现功能:
- 点击按钮截图功能。
- 屏幕遮罩截图功能:截图时透明显示屏幕,高亮显示截取的局部矩形区域。
- 截图显示、并显示图像大小、保存图像功能。
- 鼠标移动显示鼠标点位置及位置对应的RGB颜色。
-
主要内容:
- 实现局部截图功能需要创建下面的方法:该方法是DPI感知声明:启动时调用,应对屏幕缩放。如果不创建并调用该方法,屏幕缩放时会获取不到真正的区域。
[System.Runtime.InteropServices.DllImport("user32.dll")] private static extern bool SetProcessDPIAware();- 除了实现上面的方法,还有一些参数需要设置,是实现桌面遮罩,然后局部截图的。
this.FormBorderStyle = FormBorderStyle.None; //无边框 this.WindowState = FormWindowState.Maximized; //最大化 this.Location = Point.Empty; //桌面左上角起始位 this.DoubleBuffered = true; //双缓冲 this.AllowTransparency = true; //可调整透明度 this.TransparencyKey = SystemColors.Control; //透明区域颜色 this.TopMost = true; //顶层窗体 this.Opacity = 0.5; //透明度 this.BackColor = Color.White; //背景色 this.Cursor = Cursors.Cross; //十字光标
-
-
预览
-
主界面
-
截图
-
-
代码
-
主窗体
public partial class MainForm : Form { public MainForm() { InitializeComponent(); this.CenterToParent(); this.Location = new Point(100,100); this.MaximumSize = this.Size; this.MinimumSize = this.Size; } private void Btn_Screenshot_Click(object sender, EventArgs e) { try { this.Hide(); FrmScreenshotEditor frmScreenshotEditor = new FrmScreenshotEditor(); FrmScreenshot frmScreenshot = new FrmScreenshot(frmScreenshotEditor); frmScreenshot.ShowDialog(); if (frmScreenshotEditor.Image == null) { this.Show(); return; } else { Image image = new Bitmap(frmScreenshotEditor.Image); frmScreenshotEditor.PixelSize = image.Size; Debug.WriteLine($"图像大小:X = {image.Width},Y = {image.Height}"); frmScreenshotEditor.ShowDialog(); this.Show(); } } catch (Exception ex) { this.Show(); } } }-
截图窗体
public partial class FrmScreenshot : Form { #region 字段|属性 private Point startPoint; private Point endPoint; private Point mouseLastPoint; private bool isSelecting = false; FrmScreenshotEditor frm_ScreenshotEidtor; #endregion #region 初始化|加载 public FrmScreenshot() { InitializeComponent(); } public FrmScreenshot(FrmScreenshotEditor form) { InitializeComponent(); SetProcessDPIAware(); frm_ScreenshotEidtor = form; } private void FrmScreenshot_Load(object sender, EventArgs e) { this.FormBorderStyle = FormBorderStyle.None; //无边框 this.WindowState = FormWindowState.Maximized; //最大化 this.Location = Point.Empty; //桌面左上角起始位 this.DoubleBuffered = true; //双缓冲 this.AllowTransparency = true; //可调整透明度 this.TransparencyKey = SystemColors.Control; //透明区域颜色 this.TopMost = true; //顶层窗体 this.Opacity = 0.5; //透明度 this.BackColor = Color.White; //背景色 this.Cursor = Cursors.Cross; //十字光标 } #endregion #region 方法|自定义方法 /// <summary> /// DPI感知声明:启动时调用,应对屏幕缩放 /// </summary> [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern bool SetProcessDPIAware(); //采集选择区域 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; } // 显示(绘制)鼠标位置文本 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); } } //绘制选择矩形 private void DrawSelectedRegion(Graphics graphics, Rectangle selectionRect) { // 绘制半透明背景 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 #region 重写事件方法 protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left) { startPoint = e.Location; isSelecting = true; } else if (e.Button == MouseButtons.Right) { this.Close(); // 右键取消 } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (isSelecting) { endPoint = e.Location; this.Invalidate(); } else { mouseLastPoint = e.Location; this.Invalidate(); } } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (e.Button == MouseButtons.Left) { isSelecting = false; if (frm_ScreenshotEidtor != null) { this.Opacity = 0; Thread.Sleep(100); this.Refresh(); frm_ScreenshotEidtor.Image = CaptureSelectedRegion(); } this.Close(); } } protected override void OnMouseHover(EventArgs e) { base.OnMouseHover(e); } protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if (e.KeyCode == Keys.Escape) { this.Close(); // ESC键取消 } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (isSelecting) { DrawMousePosition(e.Graphics); } else { using (Pen pen = new Pen(Color.Blue, 1)) { int x = mouseLastPoint.X; int y = mouseLastPoint.Y; int width = 100; int height = 100; string sizeText = $"{x} x {y}"; Font font = new Font("宋体", 14); SizeF textSize = e.Graphics.MeasureString(sizeText, font); float textX = x + width - textSize.Width - 5; float textY = y + height - textSize.Height - 5; // 绘制文本背景 e.Graphics.FillRectangle(Brushes.White, textX - 2, textY - 2,textSize.Width + 4, textSize.Height + 4); // 绘制文本 e.Graphics.DrawString(sizeText, font, Brushes.Blue, x-20, y); } } } #endregion }-
截图编辑窗体
public partial class FrmScreenshotEditor :Form { #region 字段|属性 private Size pixelSize = new Size(0,0); public Image Image { get => uvCanvas.Image; set { uvCanvas.Image = value; label_PixelSize.Text = $"像素:{PixelSize.Width}×{PixelSize.Height}"; Invalidate(); } } public Size PixelSize { get => pixelSize; set { pixelSize = value; label_PixelSize.Text = $"像素:{PixelSize.Width}×{PixelSize.Height}"; Invalidate(); } } #endregion #region 初始化|加载 public FrmScreenshotEditor() { this.WindowState = FormWindowState.Maximized; InitializeComponent(); this.Size = new Size(1000, 700); uvCanvas.ImagePixPointEvent += UvCanvas_ImagePixPointEvent; } private void UvCanvas_ImagePixPointEvent(PointF e) { Debug.WriteLine($"当前图像点:{(int)e.X},{(int)e.Y}"); } #endregion #region 按钮事件方法 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}"); } } } private void BtnUndo_Click(object sender, EventArgs e) { } private void BtnRedo_Click(object sender, EventArgs e) { } private void BtnAddText_Click(object sender, EventArgs e) { } #endregion #region 重写事件方法 protected override void OnResize(EventArgs e) { base.OnResize(e); } #endregion }-
自定义控件代码
public partial class UVCanvas : Control { #region 字段|属性 public delegate void ImagePixPointCallback(PointF pointF); public event ImagePixPointCallback ImagePixPointEvent; //基本参数 private Image _image; //图像 private Point _lastMousePosition; //鼠标最后位置 private PointF _imagePoint = new PointF(0, 0); //图像位置点 private PointF _imagePosition = new PointF(0, 0); //图像位置 private float _zoom = 1.0f; //当前缩放 private const float MinZoom = 0.15f; //最小缩放 private const float MaxZoom = 10.0f; //最大缩放 private const float ZoomStep = 0.1f; //缩放步长 //控件背景棋盘格参数 private int controlGridSize = 20; // 每个格子的大小 private Color backColor1 = Color.LightGray; // 第一种颜色 private Color backColor2 = Color.WhiteSmoke; // 第二种颜色 //属性 public Image Image { get => _image; set { _image = value; CenterImage(); Invalidate(); } } /// <summary> /// 当前像素点 /// </summary> public PointF ImagePoint { get => _imagePoint;private set => _imagePoint = value; } #endregion #region 初始化 public UVCanvas() { this.DoubleBuffered = true; this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); } #endregion #region 其他方法 private PointF ScreenToImage(Point screenPoint) { return new PointF((screenPoint.X - _imagePosition.X) / _zoom, (screenPoint.Y - _imagePosition.Y) / _zoom); } /// <summary> /// 获取目标矩形 /// </summary> private RectangleF GetDestinationRectangle() { if (_image == null) return RectangleF.Empty; return new RectangleF(_imagePosition.X, _imagePosition.Y, (_image.Width * _zoom), (_image.Height * _zoom)); } /// <summary> /// 图像居中 /// </summary> private void CenterImage() { if (_image == null) return; _imagePosition = new PointF((this.Width - _image.Width * _zoom) / 2, (this.Height - _image.Height * _zoom) / 2); Invalidate(); } public void ResetImage() { _zoom = 1.0f; CenterImage(); } /// <summary> /// 获取像素值 /// </summary> private Color GetPixelColor(PointF imagePoint) { if (_image == null ||imagePoint.X < 0 || imagePoint.X >= _image.Width || imagePoint.Y < 0 || imagePoint.Y >= _image.Height) { return Color.Empty; // 无效坐标 } // 如果是 Bitmap,直接读取像素 if (_image is Bitmap bitmap) { return bitmap.GetPixel((int)imagePoint.X, (int)imagePoint.Y); } else { // 如果是其他 Image 类型(如 Metafile),先转换为 Bitmap using (Bitmap tempBitmap = new Bitmap(_image)) { return tempBitmap.GetPixel((int)imagePoint.X, (int)imagePoint.Y); } } } #region 绘制网格 /// <summary> /// 绘制控件棋盘格背景 /// </summary> private void DrawControlGridBackgroud(PaintEventArgs e) { // 获取绘制区域 Rectangle clipRect = e.ClipRectangle; // 计算需要绘制的起始行列 int startX = (int)(clipRect.Left / controlGridSize) * controlGridSize; int startY = (int)(clipRect.Top / controlGridSize) * controlGridSize; // 循环绘制棋盘格 for (int y = startY; y < clipRect.Bottom; y += controlGridSize) { for (int x = startX; x < clipRect.Right; x += controlGridSize) { // 根据位置绘制颜色 bool isAlternate = ((x / controlGridSize) + (y / controlGridSize)) % 2 == 0; using (Brush brush = new SolidBrush(isAlternate ? backColor1 : backColor2)) { e.Graphics.FillRectangle(brush, x, y, controlGridSize, controlGridSize); } } } } #endregion #endregion #region 事件方法重写 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); DrawControlGridBackgroud(e); if (_image == null) return; e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; RectangleF destRect = GetDestinationRectangle(); e.Graphics.DrawImage(_image, destRect); // 新增:绘制当前鼠标位置的图像坐标 string coordText = $"({ImagePoint.X:F1}, {ImagePoint.Y:F1})"; // 新增:获取 RGB 值 Color pixelColor = GetPixelColor(ImagePoint); using (var font = new Font("Arial", 10)) using (var brush = new SolidBrush(Color.Red)) { //判断显示 RGB 值 if (pixelColor != Color.Empty) { string rgbText = $"RGB: ({pixelColor.R}, {pixelColor.G}, {pixelColor.B})"; coordText = $"{coordText}\r\n{rgbText}"; } e.Graphics.DrawString(coordText, font, brush, 10, 10); } } protected override void OnResize(EventArgs e) { base.OnResize(e); CenterImage(); } protected override void OnDoubleClick(EventArgs e) { base.OnDoubleClick(e); ResetImage(); } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left && _image != null) { _lastMousePosition = e.Location; this.Cursor = Cursors.Hand; } } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); this.Cursor = Cursors.Default; } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (_image != null) { // 获取图像在屏幕上的绘制区域 ImagePoint = ScreenToImage(e.Location); // 检查鼠标是否在图像区域内 RectangleF imageRect = GetDestinationRectangle(); //计算图像坐标(仅在图像区域内计算) if (imageRect.Contains(e.Location)){ ImagePoint = ScreenToImage(e.Location);} // 不在图像内,设为 (-1, -1) else { ImagePoint = new PointF(-1, -1); } // 回调图像点 ImagePixPointEvent?.Invoke(ImagePoint); Invalidate(); } if (e.Button == MouseButtons.Left && _image != null) { // 计算鼠标移动增量 _imagePosition.X += (e.X - _lastMousePosition.X); _imagePosition.Y += (e.Y - _lastMousePosition.Y); // 记录鼠标最后位置 _lastMousePosition = e.Location; Invalidate(); } } protected override void OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); if (_image == null) return; // 1. 记录缩放前鼠标在 "图像坐标" 中的位置 PointF mouseInImageSpace = ScreenToImage(e.Location); // 2. 计算新的缩放级别 _zoom += (e.Delta > 0 ? ZoomStep : -ZoomStep); _zoom = Math.Max(MinZoom, Math.Min(MaxZoom, _zoom)); // 3. 计算缩放后该点在屏幕上的新位置(基于鼠标点缩放) PointF mouseInScreenSpaceAfterZoom = new PointF( mouseInImageSpace.X * _zoom + _imagePosition.X, mouseInImageSpace.Y * _zoom + _imagePosition.Y); // 4. 计算偏移量,使鼠标下的点仍然在原来的屏幕位置 _imagePosition.X += (e.Location.X - mouseInScreenSpaceAfterZoom.X); _imagePosition.Y += (e.Location.Y - mouseInScreenSpaceAfterZoom.Y); // 更新显示 Invalidate(); } #endregion } -
-
总结
- 通过实现案例学习编程是一种高效的学习方法。这种学习方式将抽象概念具体化。通过上面的截图程序,我们实际应用了窗体控件、事件处理、GDI+绘图等核心知识点,比单纯学习理论更容易理解和记忆。
- 其次通过案例可以获得正向反馈,看到自己编写的代码真正实现功能,极大提升了学习动力和成就感。
- 最后,该案例后面可以自己扩展性,当你掌握了更多的基础知识后,可以逐步添加区域选择、图像编辑等更高级的功能。
-
最后
- 项目源码: gitee.com/incodenotes…
- 如果你觉得这篇文章对你有帮助,不妨点个赞再走呗!
- 如有其他疑问,欢迎评论区留言讨论!
- 也可以关注微信公众号 [编程笔记in] ,一起交流学习!
❖ 感 谢 您 的 关 注 ❖