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
随机推荐
- [D3] Load and Inspect Data with D3 v4
You probably use a framework or standalone library to load data into your apps, but what if that’s o ...
- Android 带文字的图片分享
这里也记录下上下文,因为做了一个失物招领的App,当有人上交了失物之后,可以将这个消息分享出去,这个消息内容有物品的信息和图片,而微信SDK始终无法做到,就想着把物品信息嵌入到图片中分享出去,先放一个 ...
- 移动端UI界面设计:APP字体排版设计的七个原则
移动端UI界面设计:APP字体排版设计的七个原则 发布于: 2015 年 2 月 9 日 by admin 再来谈移动端APP字体排版设计,也许有人会说,这个还有什么好说的呢?但是真正能够运用好APP ...
- js调用百度地图api
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...
- Angular.js回想+学习笔记(1)【ng-app和ng-model】
Angular.js中index.html简单结构: <!doctype html> <html ng-app> <head> <script src=&qu ...
- html5--6-33 CSS定位是什么
html5--6-33 CSS定位是什么 一.总结 一句话总结: 1.常规文档流是一套体系,浮动是另外一套体系. 2.标签清除浮动之后会跑到常规文档流它本来的地方. 3.浮动是否占据常规文档流:应该不 ...
- 前端css实现最基本的时间轴
原型: 图片.png 代码: <!DOCTYPE html > <html> <head> <link rel="stylesheet" ...
- POJ 1985 - 树的直径
传送门 题目大意 给一颗n个点的树,求树的直径(最长的一条链) 题解 先随便找一个点u,dfs出离它最远的点v 于是有以下情况: 直径就是这条链 直径经过u,是这条链的延长 直径不经过u 只需要从v再 ...
- Fiddler快速入门(还有一个功能就是不经过网络,直接模拟一个响应返回给客户端)
Fiddler是一个免费.强大.跨平台的HTTP抓包工具.Wireshark也是一个强大的抓包工具,不过Wireshark是一个通用的抓包工具,主要精力放在各种协议上了,针对HTTP的特定功能较少.所 ...
- windows server 安装 mysql – 畅玩Coding
原文:windows server 安装 mysql – 畅玩Coding windows server 安装 mysql 2018年12月11日2018年12月11日 admin 下载:https: ...