.NET 控件转图片
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 控件转图片的更多相关文章
- MFC编程入门之二十七(常用控件:图片控件PictureControl)
上一节讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文本框都是 ...
- File控件选择图片的时候在Html5下马上预览
页面HTML <div> <img src="@pic.Path" id="img" style="width:200px;heig ...
- iOS开发UI篇—UIScrollView控件实现图片缩放功能
iOS开发UI篇—UIScrollView控件实现图片缩放功能 一.缩放 1.简单说明: 有些时候,我们可能要对某些内容进行手势缩放,如下图所示 UIScrollView不仅能滚动显示大量内容,还能对 ...
- iOS开发UI篇—UIScrollView控件实现图片轮播
iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播 二.实现代码 storyboard中布局 代码: #import "YYV ...
- iOS开发——UI高级OC篇&自定义控件之调整按钮中子控件(图片和文字)的位置
自定义控件之调整按钮中子控件(图片和文字)的位置 其实还有一种是在storyBoard中实现的,只需要设置对应空间的左右间距: 这里实现前面两种自定义的方式 一:imageRectForContent ...
- 【转】 iOS开发UI篇—UIScrollView控件实现图片轮播
原文:http://www.cnblogs.com/wendingding/p/3763527.html iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播 ...
- VS2010/MFC常用控件:图片控件Picture Control
图片控件Picture Control 本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到 ...
- 让DELPHI自带的richedit控件显示图片
让DELPHI自带的richedit控件显示图片 unit RichEx; { 2005-03-04 LiChengbin Added: Insert bitmap or gif into RichE ...
- VS2010/MFC编程入门之二十七(常用控件:图片控件Picture Control)
上一节中鸡啄米讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文 ...
- 任意flex控件导出图片
任意flex控件导出图片 flex导出图片功能通常是: 思路1:客户端将UIComponent转化为BitmapData,再转为ByteArray,将ByteArray上传到服务端,服务端发送文件 ...
随机推荐
- 使用rem、动态vh自适应移动端
前言 这是我的 模仿抖音 系列文章的第六篇 第一篇:200行代码实现类似Swiper.js的轮播组件 第二篇:实现抖音 "视频无限滑动"效果 第三篇:Vue 路由使用介绍以及添加转 ...
- Qt程序运行报错
报错内容 PC环境为Ubuntu20.04,Qt版本是Qt5.12.9,AsensingViewer是编译好的程序 ./AsensingViewer: error while loading shar ...
- MyBatis实现MySQL表字段及结构的自动增删
前言 在开发过程中,总会涉及到数据库表结构字段的增加或者删除,或者是索引的增加和减少,这个时候能把修改表结构字段这些工作都交给程序来进行,那能大大方便开发.正好有一个现成的工具可以在springboo ...
- 🐞vue兄弟组件中方法互相调用
场景:父组件中同时引入两个子组件(A和B),此时B组件点击按钮需要调用A组件里面的方法 方案1:vue的事件总线 方案2:自定义事件($emit) 最终方案:方案2 父组件 具体操作 B组件上添加一个 ...
- Java中Calendar类与SimpleDateFormat类的介绍
目录 Calendar类(关于日期的一些方法) get(Calendar.XXX); get(Calendar.Year) get(Calendar.MONTH) get(Calendar.DAY_O ...
- Vulkan Support Check and Dynamic Loader C++ code sample
很多时候不想静态依赖VulkanSDK所提供的静态库,因为会遇到一些过早的电脑不支持vulkan, 那么就需要使用动态加载vulkan-1.dll(for Windows)或libMoltenVK.d ...
- tomcat部署Jenkins
安装环境 jdk 1.8 tomcat 9.0 jenkins 2.290 准备工作 安装好Tomcat,8080端口启动 安装好jdk,配置好环境变量 ECS服务器安全组放开8080端口 关闭防火墙 ...
- ABC330
D 记录每一行,每一列有多少个 o,然后统计答案即可. code E 想到 \(mex^{i \le n}_{i = 1} a_i \le n\) 这整个题就可做了(赛时因为没想到这个,痛失 \(47 ...
- flutter 创建第一个项目(二)
新建flutter project 项目 这样就创建出了第一个项目
- python xlrd excel读取操作
import xlrd wb = xlrd.open_workbook("test.xlsx") sheet1 = wb.sheets()[1] # 通过索引顺序获取 #table ...