在 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; }
}

以上代码的预期行为是什么?还请大家跑跑试试

其实就是界面在做动画,只是此动画有些有趣,需要在界面有其他逻辑进行界面刷新的时候,或者说触发渲染线程进行渲染时,才会进行动画刷新

本文所有代码放在 githubgitee 欢迎小伙伴访问

WPF 更改 DrawingVisual 的 RenderOpen 用到的对象的内容将持续影响渲染效果的更多相关文章

  1. wpf 客户端【JDAgent桌面助手】开发详解(三) 瀑布流效果实现与UI虚拟化优化大数据显示

    目录区域: 业余开发的wpf 客户端终于完工了..晒晒截图 wpf 客户端[JDAgent桌面助手]开发详解-开篇 wpf 客户端[JDAgent桌面助手]详解(一)主窗口 圆形菜单... wpf 客 ...

  2. WPF中实现图片文件转换成Visual对象,Viewport3D对象转换成图片

    原文:WPF中实现图片文件转换成Visual对象,Viewport3D对象转换成图片 1.图片文件转换成Visual对象 private Visual CreateVisual(string imag ...

  3. [WPF系列]-数据邦定之DataTemplate 根据对象属性切换模板

      引言 书接上回[WPF系列-数据邦定之DataTemplate],本篇介绍如何根据属性切换模板(DataTemplate)   切换模板的两种方式:   使用DataTemplateSelecto ...

  4. wpf 中 Ellipse 对象对动画性能的影响

    vs2019 .NetFramework 4.8 win10-64 1909 接手一个wpf项目,某窗口中包含大量的 Shape 对象(线,矩形,圆形等). 这些内容要匀速的向左平移,类似于游戏&qu ...

  5. WinForm小白的WPF初试一:从PropertyGrid控件,输出内容到Word(上)

    学WinForm也就半年,然后转到WPF,还在熟悉中.最近拿到一个任务:从PropertyGrid控件,输出内容到Word.难点有: 一.PropertyGrid控件是WinForm控件,在WPF中并 ...

  6. WPF MVVM模式下实现ListView下拉显示更多内容

    在手机App中,如果有一个展示信息的列表,通常会展示很少一部分,当用户滑动到列表底部时,再加载更多内容.这样有两个好处,提高程序性能,减少网络流量.这篇博客中,将介绍如何在WPF ListView中实 ...

  7. WPF前台界面显示“未将对象引用设置到对象的实例”

    在做即时通信项目中,使用WPF的MVVM模式,如果在前台绑定VM,经常会显示波浪线,鼠标放上去提示未将对象引用设置到对象的实例,但程序能正常运行,后来发现如果前台不绑定VM,在后台cs里绑定就不会出现 ...

  8. C# WPF 登录多线程中 “调用线程无法访问对象,因为另一个线程拥有该对象“

    造成这个错误的原因很多,以下是我遇到的 我的思路,开启一个线程A登录.因为服务器响应登录成功需要在主线程做一些操作,我这边需要用到主线程的窗口对象,我把窗口对象传到线程 A,直接用实例方法会有这个错误 ...

  9. WPF 远程显示原图 当前主页面 工具栏 一个Window页面的元素适用一个效果

    http://www.jb51.net/article/98384.htm 1.wpf远程显示原图: Stretch="Fill" + ; 主要是因为那个950和650,据显示位置 ...

  10. WPF:将HTML RGB颜色值转化为Color对象的两种方式

    (1)方式一: Color color1 = (Color)System.Windows.Media.ColorConverter.ConvertFromString("#E0E0E0&qu ...

随机推荐

  1. 三维模型3DTile格式轻量化在数据存储的重要性分析

    三维模型3DTile格式轻量化在数据存储的重要性分析 三维模型3DTile格式轻量化在数据存储中占有重要地位.随着科技的不断发展,尤其是空间信息科技的进步,人们对于三维地理空间数据的需求日益增长.然而 ...

  2. PyQt5报错:This application failed to start because no Qt platform plugin could be initialized

    问题背景: 想使用PyQt5来创建一个可视化窗口,先在pycharm里面安装PyQt5,版本为5.14.0.之后在代码中调用此包:from PyQt5 import QtCore, QtGui, Qt ...

  3. Oracle regexp_replace 手机号脱敏

    select '18012345678',regexp_replace('18012345678','(.){4}','****',4,1) from dual;

  4. Java数据类型转换,字符串(String)转日期(Date)

    Java类型转换,字符串(String)转日期(Date) import java.text.ParseException; import java.text.SimpleDateFormat; im ...

  5. Jetpack Compose(4)——重组

    目录 一.状态变化 1.1 状态变化是什么 1.2 mutableStateListOf 和 mutableStateMapOf 二.重组的特性 2.1 Composable 重组是智能的 2.2 C ...

  6. #树状数组,离散#C 波动序列

    分析 设\(dp[i][j][0/1/2/3]\)表示前\(i\)个位置当前选的数为\(j\), 且选择的是第一行/第二行/第三行不下降/第三行不上升, 状态转移方程显然,用线段树或者树状数组维护一下 ...

  7. go~istio加载wasm的步骤

    参考 https://github.com/higress-group/proxy-wasm-go-sdk/tree/main/proxywasm https://github.com/tetrate ...

  8. 常用的Numpy通用函数列表

    官网来源:Universal functions (ufunc) - NumPy v1.21 Manual 数学运算(Math operations) 表达式 定义 add(x1, x2, /[, o ...

  9. 自动编号工具类:NumAutoUtils详解

    在软件开发中,经常需要生成唯一的编号,例如订单号.发票号.实验编号等.为了简化这一过程,本文将介绍一个Java工具类NumAutoUtils,它可以帮助我们生成带有前缀和日期的自动编号. 概述 Num ...

  10. openGauss每日一练第四天

    openGauss 每日一练第四天 本文出处:https://www.modb.pro/db/193083 学习地址 https://www.modb.pro/course/133 学习目标 学习 o ...