WPF 更改 DrawingVisual 的 RenderOpen 用到的对象的内容将持续影响渲染效果
在 WPF 里面,可以通过 DrawingVisual 来进行使用底层的绘制方法,此方法需要调用 DrawingVisual 的 RenderOpen 拿到 DrawingContext 类型的对象,接着调用此对象的方法来进行界面绘制。在绘制完成之后,如果依然保存绘制过程的对象,例如 Transform 对象,那当界面再次刷新时,如果更改此对象的属性,将会影响渲染
似乎这不是一个可以做简单描述的问题,其实这个问题也让我前天花了半天的时间才解决的一个界面渲染问题的其中一个。我在编写一个简单的轻量的文本库的时候,发现了文本字排版存在了一点问题。我的文本排版才能的是将文本转换为 Geometry 对象,接着在 DrawingContext 里面绘制出来。我为了实现让文本可以叠加特效的功能,因此不采用 GlyphRun 类型,同时为了减少 Geometry 对象的创建,我不能在 Geometry 对象上叠加变换
因为为了让文本的字能排版对,我就需要设置每个字在界面绘制的坐标。为了简化逻辑,我采用一个 RectangleGeometry 来代替文字的 Geometry 对象。如基础的知识,在 DrawingContext 里面如果想要在指定的地方绘制某个内容,可以采用的方法是调用 PushTransform 方法,设置当前绘制的变换,也就包括了设置当前绘制在哪,如下面代码
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
for (int i = 0; i < 10; i++)
{
var translateTransform = new TranslateTransform();
translateTransform.X = i * 15;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
此时的界面能工作,大概如下

然而我看到了每次都需要创建一个 TranslateTransform 对象,我觉得也许会影响内存。是否 TranslateTransform 对象可以和 RectangleGeometry 对象一样复用。在调用 Pop 方法之后,是否 TranslateTransform 对象的内容已被拷贝,于是我变更代码如下
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
var translateTransform = new TranslateTransform();
for (int i = 0; i < 10; i++)
{
translateTransform.X = i * 15 + 10;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
此时的 TranslateTransform 是复用的,然而界面就不能很好工作,所有的矩形都会绘制到最后的地方。看起来 PushTransform 内部没有拷贝 TranslateTransform 的对象,只是记录这条指令而已

从以上的例子可以看到在 DrawingContext 里面绘制的内容,其实调用 PushTransform 方法只是将传入的 TranslateTransform 进行记录,而没有进行更多的拷贝。在后续变更 TranslateTransform 时,将会在渲染的时候,读取到变更之后的 TranslateTransform 对象的属性
在调用 DrawingVisual 的 RenderOpen 之后,在 DrawingContext 里面调用绘制方法时,不是立刻进行绘制,而是收集绘制的指令。实际的绘制渲染是在渲染线程通过 DirectX 等来实现的
在 RenderOpen 关闭之后,对 TranslateTransform 对象的变更也会影响到最终的渲染结果,因为 RenderOpen 关闭时不是立刻进行渲染。如下面代码,将会让所有的绘制的矩形都放在 X 是 500 的地方
var drawingVisual = new DrawingVisual();
var translateTransform = new TranslateTransform();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
for (int i = 0; i < 10; i++)
{
translateTransform.X = i * 15;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
translateTransform.X = 500;
那如果再做一些更有趣的事情呢?我在不断的更改 TranslateTransform 的属性,如下面代码
class Foo : UIElement
{
public Foo()
{
var drawingVisual = new DrawingVisual();
var translateTransform = new TranslateTransform();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
for (int i = 0; i < 10; i++)
{
translateTransform.X = i * 15;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
translateTransform.X = 500;
Visual = drawingVisual;
SetTranslateTransform(translateTransform);
}
private async void SetTranslateTransform(TranslateTransform translateTransform)
{
while (true)
{
translateTransform.X++;
if (translateTransform.X > 700)
{
translateTransform.X = 0;
}
await Task.Delay(TimeSpan.FromMilliseconds(10));
}
}
protected override Visual GetVisualChild(int index) => Visual;
protected override int VisualChildrenCount => 1;
private Visual Visual { get; }
}
以上代码的预期行为是什么?还请大家跑跑试试
其实就是界面在做动画,只是此动画有些有趣,需要在界面有其他逻辑进行界面刷新的时候,或者说触发渲染线程进行渲染时,才会进行动画刷新
本文所有代码放在 github 和 gitee 欢迎小伙伴访问
WPF 更改 DrawingVisual 的 RenderOpen 用到的对象的内容将持续影响渲染效果的更多相关文章
- wpf 客户端【JDAgent桌面助手】开发详解(三) 瀑布流效果实现与UI虚拟化优化大数据显示
目录区域: 业余开发的wpf 客户端终于完工了..晒晒截图 wpf 客户端[JDAgent桌面助手]开发详解-开篇 wpf 客户端[JDAgent桌面助手]详解(一)主窗口 圆形菜单... wpf 客 ...
- WPF中实现图片文件转换成Visual对象,Viewport3D对象转换成图片
原文:WPF中实现图片文件转换成Visual对象,Viewport3D对象转换成图片 1.图片文件转换成Visual对象 private Visual CreateVisual(string imag ...
- [WPF系列]-数据邦定之DataTemplate 根据对象属性切换模板
引言 书接上回[WPF系列-数据邦定之DataTemplate],本篇介绍如何根据属性切换模板(DataTemplate) 切换模板的两种方式: 使用DataTemplateSelecto ...
- wpf 中 Ellipse 对象对动画性能的影响
vs2019 .NetFramework 4.8 win10-64 1909 接手一个wpf项目,某窗口中包含大量的 Shape 对象(线,矩形,圆形等). 这些内容要匀速的向左平移,类似于游戏&qu ...
- WinForm小白的WPF初试一:从PropertyGrid控件,输出内容到Word(上)
学WinForm也就半年,然后转到WPF,还在熟悉中.最近拿到一个任务:从PropertyGrid控件,输出内容到Word.难点有: 一.PropertyGrid控件是WinForm控件,在WPF中并 ...
- WPF MVVM模式下实现ListView下拉显示更多内容
在手机App中,如果有一个展示信息的列表,通常会展示很少一部分,当用户滑动到列表底部时,再加载更多内容.这样有两个好处,提高程序性能,减少网络流量.这篇博客中,将介绍如何在WPF ListView中实 ...
- WPF前台界面显示“未将对象引用设置到对象的实例”
在做即时通信项目中,使用WPF的MVVM模式,如果在前台绑定VM,经常会显示波浪线,鼠标放上去提示未将对象引用设置到对象的实例,但程序能正常运行,后来发现如果前台不绑定VM,在后台cs里绑定就不会出现 ...
- C# WPF 登录多线程中 “调用线程无法访问对象,因为另一个线程拥有该对象“
造成这个错误的原因很多,以下是我遇到的 我的思路,开启一个线程A登录.因为服务器响应登录成功需要在主线程做一些操作,我这边需要用到主线程的窗口对象,我把窗口对象传到线程 A,直接用实例方法会有这个错误 ...
- WPF 远程显示原图 当前主页面 工具栏 一个Window页面的元素适用一个效果
http://www.jb51.net/article/98384.htm 1.wpf远程显示原图: Stretch="Fill" + ; 主要是因为那个950和650,据显示位置 ...
- WPF:将HTML RGB颜色值转化为Color对象的两种方式
(1)方式一: Color color1 = (Color)System.Windows.Media.ColorConverter.ConvertFromString("#E0E0E0&qu ...
随机推荐
- Java内存马1-传统web内存马
1.前置知识 (1)Tomcat Tomcat是一个开源的.轻量级的.用于Java Servlet和JavaServer Pages(JSP)的Web应用程序服务器.它是Apache软件基金会的一个项 ...
- Linux开启SSH连接
1. 查看是否安装 openssh-server:yum list installed | grep openssh-server 已安装成功,如下图 2.如果没有任何输出显示表示没有安装openss ...
- 3D Object Detection Essay Reading 2024.03.27
Point Transformer V3: Simpler, Faster, Stronger publish:CVPR2024 paper:https://arxiv.org/abs/2312.10 ...
- Base64编码的全面介绍
1. Base64的定义和作用 Base64是一种用64个字符表示二进制数据的编码方式,通常用于在网络传输中将二进制数据转换为可打印字符的形式.Base64编码后的数据由大小写字母.数字和特殊字符组成 ...
- C++ atomic
atomic 每个 std::atomic 模板的实例化和全特化定义一个原子类型.若一个线程写入原子对象,同时另一线程从它读取,则行为良好定义. 另外,对原子对象的访问可以建立线程间同步,并按 std ...
- 5W1H聊开源之Who和How——谁、如何参与开源?
上次Who的主体是谁"发明"了开源,这一次主体转换,来看看开源发明之后,还有哪些人为开源做贡献?作为普通程序员的我们,又能以怎样的形式参与到开源项目中? 很多人都以为参与开源是一件 ...
- #期望dp#CF1810G The Maximum Prefix
洛谷题面 CF1810G 分析 考虑最大前缀和满足两个条件,就是所有前缀和都不超过,以及一定有一个等于. 那么就要保证它能达到最大值且一直不能高于它 设 \(dp[i][j][0/1]\) 表示前 \ ...
- #主席树,Dijkstra,哈希#CF464E The Classic Problem
题目 边权均为2的幂次的无向图,求 \(S\) 到 \(T\) 的最短路 \(n,m\leq 10^5\) 分析 最短路直接考虑用 Dijkstra,它需要维护松弛操作和堆, 那么也就是要重载加号和小 ...
- 玩转OpenHarmony PID:教你打造两轮平衡车
简介 此次为大家带来的是OpenAtom OpenHarmony(以下简称"OpenHarmony")系统与PID控制算法相结合并落地的平衡车项目. PID控制算法是一种经典的,并 ...
- C++ 多线程编程和同步机制:详解和实例演示
C++中的多线程编程和同步机制使得程序员可以利用计算机的多核心来提高程序的运行效率和性能.本文将介绍多线程编程和同步机制的基本概念和使用方法. 多线程编程基础 在C++中,使用<thread&g ...