Windows应用开发有很多场景需要动态获取控件显示的图像,即控件转图片,用于其它界面的显示、传输图片数据流、保存为本地图片等用途。

下面分别介绍下一些实现方式以及主要使用场景

RenderTargetBitmap

控件转图片BitmapImage/BitmapSource,在WPF中可以使用RenderTargetBitmap获取捕获控件的图像。

下面我们展示下简单快速的获取控件图片:

 1     private void CaptureButton_OnClick(object sender, RoutedEventArgs e)
2 {
3 var dpi = GetAppStartDpi();
4 var bitmapSource = ToImageSource(Grid1, Grid1.RenderSize, dpi.X, dpi.Y);
5 CaptureImage.Source = bitmapSource;
6 }
7 /// <summary>
8 /// Visual转图片
9 /// </summary>
10 public static BitmapSource ToImageSource(Visual visual, Size size, double dpiX, double dpiY)
11 {
12 var validSize = size.Width > 0 && size.Height > 0;
13 if (!validSize) throw new ArgumentException($"{nameof(size)}值无效:${size.Width},${size.Height}");
14 if (Math.Abs(size.Width) > 0.0001 && Math.Abs(size.Height) > 0.0001)
15 {
16 RenderTargetBitmap bitmap = new RenderTargetBitmap((int)(size.Width * dpiX), (int)(size.Height * dpiY), dpiX * 96, dpiY * 96, PixelFormats.Pbgra32);
17 bitmap.Render(visual);
18 return bitmap;
19 }
20 return new BitmapImage();
21 }

获取当前窗口所在屏幕DPI,使用控件已经渲染的尺寸,就可以捕获到指定控件的渲染图片。捕获到图片BitmapSource,即可以将位图分配给Image的Source属性来显示。

DPI获取可以参考 C# 获取当前屏幕DPI - 唐宋元明清2188 - 博客园 (cnblogs.com)

上面方法获取的是BitmapSource,BitmapSource是WPF位图的的抽象基类,继承自ImageSource,因此可以直接用作WPF控件如Image的图像源。RenderTargetBitmap以及BitmapImage均是BitmapSource的派生实现类

RenderTargetBitmap此处用于渲染Visual对象生成位图,RenderTargetBitmap它可以用于拼接、合并(上下层叠加)、缩放图像等。BitmapImage主要用于从文件、URL及流中加载位图。

而捕获返回的基类BitmapSource可以用于通用位图的一些操作(如渲染、转成流数据、保存),BitmapSource如果需要转成可以支持支持更高层次图像加载功能和延迟加载机制的BitmapImage,可以按如下操作:

 1     /// <summary>
2 /// WPF位图转换
3 /// </summary>
4 private static BitmapImage ToBitmapImage(BitmapSource bitmap,Size size,double dpiX,double dpiY)
5 {
6 MemoryStream memoryStream = new MemoryStream();
7 BitmapEncoder encoder = new PngBitmapEncoder();
8 encoder.Frames.Add(BitmapFrame.Create(bitmap));
9 encoder.Save(memoryStream);
10 memoryStream.Seek(0L, SeekOrigin.Begin);
11
12 BitmapImage bitmapImage = new BitmapImage();
13 bitmapImage.BeginInit();
14 bitmapImage.DecodePixelWidth = (int)(size.Width * dpiX);
15 bitmapImage.DecodePixelHeight = (int)(size.Height * dpiY);
16 bitmapImage.StreamSource = memoryStream;
17 bitmapImage.EndInit();
18 bitmapImage.Freeze();
19 return bitmapImage;
20 }

这里选择了Png编码器,先将bitmapSource转换成图片流,然后再解码为BitmapImage。

图片编码器有很多种用途,上面是将流转成内存流,也可以转成文件流保存本地文件:

1     var encoder = new PngBitmapEncoder();
2 encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
3 using Stream stream = File.Create(imagePath);
4 encoder.Save(stream);

回到控件图片捕获,上方操作是在界面控件渲染后的场景。如果控件未加载,需要更新布局下:

1     //未加载到视觉树的,按指定大小布局
2 //按size显示,如果设计宽高大于size则按sie裁剪,如果设计宽度小于size则按size放大显示。
3 element.Measure(size);
4 element.Arrange(new Rect(size));

另外也存在场景:控件不确定它的具体尺寸,只是想单纯捕获图像,那代码整理后如下:

 1     public BitmapSource ToImageSource(Visual visual, Size size = default)
2 {
3 if (!(visual is FrameworkElement element))
4 {
5 return null;
6 }
7 if (!element.IsLoaded)
8 {
9 if (size == default)
10 {
11 //计算元素的渲染尺寸
12 element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
13 element.Arrange(new Rect(new Point(), element.DesiredSize));
14 size = element.DesiredSize;
15 }
16 else
17 {
18 //未加载到视觉树的,按指定大小布局
19 //按size显示,如果设计宽高大于size则按sie裁剪,如果设计宽度小于size则按size放大显示。
20 element.Measure(size);
21 element.Arrange(new Rect(size));
22 }
23 }
24 else if (size == default)
25 {
26 Rect rect = VisualTreeHelper.GetDescendantBounds(visual);
27 if (rect.Equals(Rect.Empty))
28 {
29 return null;
30 }
31 size = rect.Size;
32 }
33
34 var dpi = GetAppStartDpi();
35 return ToImageSource(visual, size, dpi.X, dpi.Y);
36 }

控件未加载时,可以使用DesiredSize来临时替代操作,这类方案获取的图片宽高比例可能不太准确。已加载完的控件,可以通过VisualTreeHelper.GetDescendantBounds获取视觉树子元素集的坐标矩形区域Bounds。

kybs00/VisualImageDemo: RenderTargetBitmap获取控件图片 (github.com)

所以控件转BitmapSource、保存等,可以使用RenderTargetBitmap来实现

VisualBrush

如果只是程序内其它界面同步展示此控件,就不需要RenderTargetBitmap了,可以直接使用VisualBrush

具体的可以看下官网VisualBrush 类 (System.Windows.Media) | Microsoft Learn,这里做一个简单的DEMO:

 1 <Window x:Class="VisualBrushDemo.MainWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:VisualBrushDemo"
7 mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
8 <Grid>
9 <Grid.ColumnDefinitions>
10 <ColumnDefinition Width="*"/>
11 <ColumnDefinition Width="10"/>
12 <ColumnDefinition/>
13 </Grid.ColumnDefinitions>
14 <Canvas x:Name="Grid1" Background="BlueViolet">
15 <TextBlock x:Name="TestTextBlock" Text="截图测试" VerticalAlignment="Center" HorizontalAlignment="Center"
16 Width="100" Height="30" Background="Red" TextAlignment="Center" LineHeight="30" Padding="0 6 0 0"
17 MouseDown="TestTextBlock_OnMouseDown"
18 MouseMove="TestTextBlock_OnMouseMove"
19 MouseUp="TestTextBlock_OnMouseUp"/>
20 </Canvas>
21 <Grid x:Name="Grid2" Grid.Column="2">
22 <Grid.Background>
23 <VisualBrush Stretch="UniformToFill"
24 AlignmentX="Center" AlignmentY="Center"
25 Visual="{Binding ElementName=Grid1}"/>
26 </Grid.Background>
27 </Grid>
28 </Grid>
29 </Window>

CS代码:

 1     private bool _isDown;
2 private Point _relativeToBlockPosition;
3 private void TestTextBlock_OnMouseDown(object sender, MouseButtonEventArgs e)
4 {
5 _isDown = true;
6 _relativeToBlockPosition = e.MouseDevice.GetPosition(TestTextBlock);
7 TestTextBlock.CaptureMouse();
8 }
9
10 private void TestTextBlock_OnMouseMove(object sender, MouseEventArgs e)
11 {
12 if (_isDown)
13 {
14 var position = e.MouseDevice.GetPosition(Grid1);
15 Canvas.SetTop(TestTextBlock, position.Y - _relativeToBlockPosition.Y);
16 Canvas.SetLeft(TestTextBlock, position.X - _relativeToBlockPosition.X);
17 }
18 }
19
20 private void TestTextBlock_OnMouseUp(object sender, MouseButtonEventArgs e)
21 {
22 TestTextBlock.ReleaseMouseCapture();
23 _isDown = false;
24 }

左侧操作一个控件移动,右侧区域动态同步显示左侧视觉

VisualBrush.Visual可以直接绑定指定控件,一次绑定、后续同步界面变更

下面是部分代码,我们看到,VisualBrush内有监听元素的内容变更:

 1     // We need 2 ways of initiating layout on the VisualBrush root.
2 // 1. We add a handler such that when the layout is done for the
3 // main tree and LayoutUpdated is fired, then we do layout for the
4 // VisualBrush tree.
5 // However, this can fail in the case where the main tree is composed
6 // of just Visuals and never does layout nor fires LayoutUpdated. So
7 // we also need the following approach.
8 // 2. We do a BeginInvoke to start layout on the Visual. This approach
9 // alone, also falls short in the scenario where if we are already in
10 // MediaContext.DoWork() then we will do layout (for main tree), then look
11 // at Loaded callbacks, then render, and then finally the Dispather will
12 // fire us for layout. So during loaded callbacks we would not have done
13 // layout on the VisualBrush tree.
14 //
15 // Depending upon which of the two layout passes comes first, we cancel
16 // the other layout pass.
17 element.LayoutUpdated += OnLayoutUpdated;
18 _DispatcherLayoutResult = Dispatcher.BeginInvoke(
19 DispatcherPriority.Normal,
20 new DispatcherOperationCallback(LayoutCallback),
21 element);
22 _pendingLayout = true;

而显示绑定元素,VisualBrush内部是通过元素Visual.Render方法将图像给到渲染上下文:

1     RenderContext rc = new RenderContext();
2 rc.Initialize(channel, DUCE.ResourceHandle.Null);
3 vVisual.Render(rc, 0);

此类VisualBrush方案,适合制作预览显示,比如打印预览、PPT页面预览列表等

.NET 控件转图片的更多相关文章

  1. MFC编程入门之二十七(常用控件:图片控件PictureControl)

    上一节讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文本框都是 ...

  2. File控件选择图片的时候在Html5下马上预览

    页面HTML <div> <img src="@pic.Path" id="img" style="width:200px;heig ...

  3. iOS开发UI篇—UIScrollView控件实现图片缩放功能

    iOS开发UI篇—UIScrollView控件实现图片缩放功能 一.缩放 1.简单说明: 有些时候,我们可能要对某些内容进行手势缩放,如下图所示 UIScrollView不仅能滚动显示大量内容,还能对 ...

  4. iOS开发UI篇—UIScrollView控件实现图片轮播

    iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播            二.实现代码 storyboard中布局 代码: #import "YYV ...

  5. iOS开发——UI高级OC篇&自定义控件之调整按钮中子控件(图片和文字)的位置

    自定义控件之调整按钮中子控件(图片和文字)的位置 其实还有一种是在storyBoard中实现的,只需要设置对应空间的左右间距: 这里实现前面两种自定义的方式 一:imageRectForContent ...

  6. 【转】 iOS开发UI篇—UIScrollView控件实现图片轮播

    原文:http://www.cnblogs.com/wendingding/p/3763527.html iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播 ...

  7. VS2010/MFC常用控件:图片控件Picture Control

    图片控件Picture Control 本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到 ...

  8. 让DELPHI自带的richedit控件显示图片

    让DELPHI自带的richedit控件显示图片 unit RichEx; { 2005-03-04 LiChengbin Added: Insert bitmap or gif into RichE ...

  9. VS2010/MFC编程入门之二十七(常用控件:图片控件Picture Control)

    上一节中鸡啄米讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文 ...

  10. 任意flex控件导出图片

    任意flex控件导出图片   flex导出图片功能通常是: 思路1:客户端将UIComponent转化为BitmapData,再转为ByteArray,将ByteArray上传到服务端,服务端发送文件 ...

随机推荐

  1. C#异步调用Process(),后台静默调用cmd控制台

    C#调用cmd控制台操作,网上有太多的教程了,但是大多数都是执行完一条指令,退出Process,下次执行指令,再次new Process(),(只为了接收到cmd指令的回复,不然会进程阻塞,程序至此不 ...

  2. SQLServer如何监控阻塞会话

    一.查询阻塞和被阻塞的会话 SELECT r.session_id AS [Blocked Session ID], r.blocking_session_id AS [Blocking Sessio ...

  3. Flutter(一):MAC的Flutter安装指南

    官网地址 官网: https://flutter.dev Github: https://github.com/flutter/flutter Git的核心分支包括master.dev.stable. ...

  4. redis RDB AOF数据持久化

    目录 redis RDB持久化[手工持久化]: redis RDB持久化条件配置[适合用于备份]redis rdb持久化策略 redis AOF持久化 redis AOF持久化配置 redis RDB ...

  5. k8s——pod的资源配置文件详解(manifest)

    pod的资源配置文件(manifest) 详细介绍pod的资源配置文件(mannifest)的各个字段的含义 元数据 字段 是否必须 类型 含义 由用户提供 备注 name 必须 str pod的名称 ...

  6. vue3 Suspense

    在Vue.js 3中,Suspense 是一个用于处理异步组件的特殊组件,它允许你在等待异步组件加载时展示备用内容.这对于优化用户体验.处理懒加载组件或异步数据获取时非常有用.Suspense 的主要 ...

  7. RT-Thread Studio使用教程

    介绍 RT-Thread Studio是官方出品的一款专门针对RT-Thread嵌入式开发.部署.调试.测试的集成开发环境,它基于Eclipse开源项目开发,极大的提高了嵌入式开发者的开发效率,目前最 ...

  8. kettle从入门到精通 第六十七课 ETL之kettle 再谈kettle阻塞,阻塞多个分支的多个步骤

    场景:ETL沟通交流群内有小伙伴反馈,如何多个分支处理完毕之后记录下同步结果呢?或者是调用后续步骤.存储过程.三方接口等. 解决:使用步骤Blocking step进行阻塞处理即可. 1. 如下流程图 ...

  9. 大一统的监控探针采集器 cprobe

    需求背景 监控数据采集领域,比如 Prometheus 生态有非常多的 Exporter,虽然生态繁荣,但是无法达到开箱即用的大一统体验,Exporter 体系的核心问题有: 良莠不齐:有的 Expo ...

  10. k8s配置文件管理

    1.为什么要用configMap ConfigMap是一种用于存储应用所需配置信息的资源类型,用于保存配置数据的键值对,可以用来保存单个属性,也可以用来保存配置文件. 通过ConfigMap可以方便的 ...