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. WPF实现轮播图

    1.效果图 2.前端代码 <Window x:Class="LiveChartDemo.View.CarouselView" xmlns="http://schem ...

  2. CentOS7的一些常规操作

    1.启动或者重启sshd服务: systemctl stop sshd.service systemctl start sshd.service systemctl restart sshd.serv ...

  3. 01.Alpine编译glibc

    概要 本文档采用glibc2.28版本作为示例,模拟内网环境无法访问github等开源社区 为精简docker容器镜像,采用Alpine镜像,需要手动编译glibc源代码 制作编译好的glibc二进制 ...

  4. test(爱测试) 开源接口测试,敏捷测试管理平台10.2.7发布

    一:itest work 简介 itest work 开源敏捷测试管理,包含极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试,接口Mock,还有压测 ,又有丰富的统计分析,8合1工作站.可按 ...

  5. itest work(爱测试) 开源一站式接口测试&敏捷测试工作站 9.0.5. Rc4

    (一)itest work 简介 itest work (爱测试)  一站式工作站让测试变得简单.敏捷,"好用.好看,好敏捷" ,是itest wrok 追求的目标.itest w ...

  6. ETL工具-nifi干货系列 第十四讲 nifi处理器PublishKafka实战教程

    1.kettle的kafka生产者叫kafka producer,nifi中的相应处理器为PublishKafka,如下图所示: 可以很清楚的看到PublishKafka处理器支持多个版本的kafka ...

  7. 10位,13位时间戳转为C#.NET格式时间 DateTime

    10位,13位时间戳转为C#.NET格式时间 DateTime - public static DateTime ToDateTime( string timestamp) { var tz = Ti ...

  8. Jenkins技术概述与开发实战

    本文详细讲解了Jenkins的安装与配置.构建作业.流水线.构建.测试和部署的具体方法,涵盖关键概念.详细步骤及代码示例,旨在帮助专业从业者实现高效的CI/CD自动化流程. 关注作者,复旦博士,分享云 ...

  9. work06

    练习题:=============================================================第七题: 1.定义方法 isSXH(int num) 功能:判断数字n ...

  10. 报错解决 :Resolved [org.springframework.web.bind.MissingServletRequestParameterException

    报错解决 :Resolved [org.springframework.web.bind.MissingServletRequestParameterException 解决方法:RequestPar ...