OpenExpressApp:精通 WPF UI Virtualization
原文:OpenExpressApp:精通 WPF UI Virtualization
本篇博客主要说明如何使用 UI Virtualization(以下简称为 UIV) 来提升 OEA 框架中 TreeGrid 控件的性能,同时,给出了一些学习 UIV 的资源。
问题
最近对 OEA 的 TreeGrid 控件进行了比较大的改造,并使用新的控件来替换了系统中所有的 DataGrid 控件。新的 TreeGrid 控件实现了很多新的功能,(之后会写一篇文章说明),但是最后遗留了一个问题:由于使用它替换了原来的 DataGrid,而 DataGrid 默认是支持 UI Virtualization 的,当有些界面的数据量比较大时,没有支持 UIV 的TreeGrid 控件就显得有些力不从心了。为了解决这个问题,这两天看了许多文章并学习了 WPF 中 UIV 的知识,在最后终于解决了,待写下此文予以记录。
先来看看实现 UIV 前:

518 条数据,生成了 18130 个 Visuals。
其实,在解决完后看来,问题主要出在 TreeGrid 的 Template 上,直接贴上来给大家看看:
<ScrollViewer Style="{StaticResource GridTreeViewScroll}" Background="{TemplateBinding Background}"
Focusable="false"
CanContentScroll="false"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<Grid>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
<TextBlock Opacity="0.5" TextWrapping="Wrap" FontSize="36" Text="没有数据" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" FontFamily="STCaiyun" RenderTransformOrigin="0.5,0.5" Foreground="#80000000">
<TextBlock.Visibility>
<MultiBinding>
<MultiBinding.Converter>
<oeaModuleWPF:ItemsControlNoDataConverter/>
</MultiBinding.Converter>
<Binding Path="Data" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type oea:GridTreeView}}"/>
<Binding Path="Items.Count" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type oea:GridTreeView}}"/>
</MultiBinding>
</TextBlock.Visibility>
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1.5"/>
<SkewTransform AngleX="-30"/>
<RotateTransform Angle="-30"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</ScrollViewer>
其中,为了实现在列表没有数据时,显示 “没有数据” 四个字,使用了一个 Grid 包含了一个 ItemsPresenter 以及一个 TextBlock。这段代码看上去没有什么问题,所以搞了很久都没有把 UIV 调试出来,最终只有在网上耐心学习了很我 UIV 的相关知识。
解决方案
其实,相关的 UIV 知识点有那么几个:
- WPF 中的 VirtualizingStackPanel 只支持一层数据的 UIV。(这一点好像在 WPF3.5 SP1 后有所改善?)
- WPF3.5 SP1 以前的 TreeView 是不支持 UIV的。而之后的 TreeView 在默认情况下 UIV 处于关闭状态,需要手动打开。
- 实现 UIV 需要一个对应的 ScollViewer。
- ScollViewer 中的 CanContentScroll 属性为 True 时,子对象才能实现 UIV。
该属性为 True 时,ScollViewer 在 Measure 时会把当前的 ViewPort 大小传给 Content 元素。否则,它会把 Infinite 传给 Content。
同 时,由子元素(也就是 VirtualizingStackPanel)需要实现 IScollInfo 并返回 Scroll 相关信息,而 ScollViewer 则只是一个简单的视窗;这样,子元素就可以在内部实现 UIV,并告知其对应的 ScrollOwner(ScrollViewer) 相关的拖动信息。
所以,上面的 xaml 主要有两个错误:
- ScrollViewer.CanContentScroll 应该设置为 True。
- 应该把 VirtualizingStackPanel 作为 ScrollViewer 的内容元素(Content)。
修改为以下 xaml 即可:
<Grid>
<ScrollViewer Style="{StaticResource GridTreeViewScroll}" Background="{TemplateBinding Background}"
Focusable="false"
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<VirtualizingStackPanel IsItemsHost="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
<TextBlock Opacity="0.5" TextWrapping="Wrap" FontSize="36" Text="没有数据">……</TextBlock>
</Grid>
同时,注意打开 TreeView 的 UIV 支持:
public class GridTreeView : TreeView
{
static GridTreeView()
{
VirtualizingStackPanel.IsVirtualizingProperty.OverrideMetadata(typeof(GridTreeView), new FrameworkPropertyMetadata(true));
}
来看看优化后的结果:

Visuals 的数量由 1W8 降到了 3000,当行数更多时,也就保持初始生成 3000 个左右。拖动起来也明显地感觉到流畅了许多。
大功告成!
相关资源
一篇通俗易懂的 UIV 概念文章:《UI Virtualization》,其中讲到了 WPF 及 SilverLight 中的 UIV。(它还有后续的文章:《Data virtualization》,也很不错)。
之前系统中用到的 DataGrid 控件,一旦数据被分组之后,性能异常低下。原因其实也和 UIV 有关:
目前 WPF 中的控件在 Group 分组后是不支持 UI Virtualization 的,原因是当 ScrollViewer.CanContentScroll 设置为 true 时,模式由 Scroll By Pixel 变为 Scroll by Item。而分组后的控件中每一个组 GroupItem 其实就是一个 Item,这时,如果继续使用 Scroll by Item 模式,将会得到非常差的用户体验,所以 MS 决定不支持分组后的 UIV,ListBox 控件的默认模板中有一个 Trigger 当 IsGrouping 为 True 时,设置 ConContentScroll 为 False。相关的内容参见:《UI Virtualization》。其它与分组相关的 UIV 文章如下:
《WPF DataGrid Virtualization with Grouping》、《MSDN Sample Code:Grouping and Virtualization》、《Problem: ListView Virtualization》
《Virtualizing TreeViewItem》: 其中的最佳答案说到几个知识点:VirtualizingStackPanel 需要和 ScrollViewer 进行交互,同时,它只支持一层的 Virtualization。可以考虑变通地使用 ListBox/ListView 来实现假的 TreeView,这样就可以实现整个列表的虚拟化。
《WPF - Virtualizing an ItemsControl》:文中指出,ItemsControl 默认不支持 UI Virtualization,原因是它的模板中没有一个 ScrollViewer。
《Are there any tricks that will help me improve TreeView’s performance》:这个系列的文章一共3篇:《Part I》、《Part II》、《Part III》, 最后一篇说明了在如何使用 ListBox 模拟一个 TreeView,这样,由于 ListBox 本身支持 UIVirtualization,所以最后的 “TreeView” 也就支持了 UI Virtualization。类似的控件已经有人传到了 CodeProject 上:《Virtualizing Tree View (VTreeView)》,其中还正好谈到了上面的这系列文章,非常凑巧的是,它还谈到了 CodeProject上被我们系统选择来实现 TreeGrid 控件的资源:《A Versatile TreeView for WPF》。
更高级的自定义 UI Virtualization,可以先参考以下几篇文章,很不错:《Virtualizing WrapPanel》、《Implementing a virtualized panel in WPF (Avalon)》、《IScrollInfo in Avalon part I》、《IScrollInfo in Avalon part II》、《IScrollInfo in Avalon part III》。
MS 自己的相关资源:
《MSDN Control Performance》、《How to: Find a TreeViewItem in a TreeView》(如何在 UIV 的情况下找到控件)、《Changing selection in a virtualized TreeView》
OpenExpressApp:精通 WPF UI Virtualization的更多相关文章
- 精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能)
原文:精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能) 本篇博客主要说明如何使用 UI Virtualization(以下简称为 UIV) 来提 ...
- [WPF]WPF Data Virtualization和UI Virtualization
这篇博客将介绍WPF中的虚拟化技术. 1. Data Virtualization 通常情况下我们说数据虚拟化是指数据源没有完全加载,仅加载当前需要显示的数据呈现给用户.这种场景会让我们想到数据分页显 ...
- C#如何在VS2015 2017版本中编写WPF UI界面引入第三方SVG图形
原文:C#如何在VS2015 2017版本中编写WPF UI界面引入第三方SVG图形 在VS2015 2017版本中编写WPF UI界面引入第三方SVG图形 最近在写WPF界面的时候遇到一个情 ...
- UI-WPF_UI:WPF UI - DMSkin官方网站
ylbtech-UI-WPF_UI:WPF UI - DMSkin官方网站 1.返回顶部 1. QQ: 944095635 DMSkin.com 首页 论坛 定制 博客 联系 DMSkin 3. ...
- WPF UI布局之概述
在线演示:http://v.youku.com/v_show/id_XNzA5NDk2Mjcy.html 清晰版视频+代码下载:http://115.com/lb/5lbeer0m9lad 一.简单介 ...
- WPF UI 开源专贴
1.ReactiveUI https://github.com/reactiveui/ReactiveUI http://www.reactiveui.net A MVVM framework tha ...
- 关闭VS2015的WPF UI调试工具
VS菜单: 工具 > 选项 > 调试 > 常规 > 启用Xaml 的UI调试工具.把勾勾去掉.
- Open-source Tutorial - Material Design for WPF UI
安装 Material Design Themes 通过 NuGet 包管理器搜索自动安装 通过 NuGet 包管理器控制台手动安装 Install-Package MaterialDesignThe ...
- WPF UI虚拟化
ComboBox
随机推荐
- 改变HTML中超链接的显示样式 分类: C1_HTML/JS/JQUERY 2014-08-27 10:11 595人阅读 评论(0) 收藏
更详细的内容请参考:http://www.w3school.com.cn/tags/tag_a.asp HTML中的代码如下: <a class="news_title" t ...
- 【搜索引擎Jediael开发笔记】v0.1完整代码 2014-05-26 15:17 463人阅读 评论(0) 收藏
详细代码请见 E:\Project\[重要]归档代码\SearchEngine归档代码 或 https://code.csdn.net/jediael_lu/jediael/tree/10991c83 ...
- 高性能 Python —— vectorization
首先来看一段判断一个整数数是否为素数的函数,然后从计算机内部计算流程的角度对其进行分析: import math def check_prime(number): sqrt_number = math ...
- Opencv Sift算子特征提取与匹配
SIFT算法的过程实质是在不同尺度空间上查找特征点(关键点),用128维方向向量的方式对特征点进行描述,最后通过对比描述向量实现目标匹配. 概括起来主要有三大步骤: 1.提取关键点: 2.对关键点附加 ...
- 基于 Android NDK 的学习之旅-----HelloWorld
Hello World作为所有编程语言的起始阶段,占据着无法改变的地位,所有中/英/法/德/美……版本的编程教材中,hello world总是作为第一个TEST记录于书本之中,所有的编程第一步就在于此 ...
- .Net Core Socket 压力测试
原文:.Net Core Socket 压力测试 .Net Core Socket 压力测试 想起之前同事说go lang写的push service单机可以到达80万连接,于是就想测试下.Net C ...
- [Node.js] Initialize a LoopBack Node.js Project through the CLI
LoopBack is a framework built on top of Express for creating APIs. It allows you to create end-to-en ...
- erlang tcp发包速度测试
http://blog.sina.com.cn/s/blog_96b8a1540101317m.html 这段时间我们的项目遇到广播包的一些性能问题,想起之前看到yufeng老大提到的1s广播40K包 ...
- 谷歌 AI 中国中心成立,人工智能势不可挡?
昨日,谷歌在上海举办了一年一度的Google中国开发者大会.在本届大会上,谷歌云首席科学家李飞飞宣布了一个重磅消息,即在北京将成立谷歌AI中国中心.对于这个即将成立的AI中心谷歌寄予厚望,希望与中国本 ...
- 学习鸟哥的Linux私房菜笔记(15)——文件系统
一.文件结构 为了能在设备上存储与读取文件,我们需要在分区上创立文件系统 文件系统记录目录与文件我们称之为文件结构 每一个文件系统在Linux里都被解释成由一个根目录为起点的目录结构 Linux将各个 ...