原文: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 知识点有那么几个:

  1. WPF 中的 VirtualizingStackPanel 只支持一层数据的 UIV。(这一点好像在 WPF3.5 SP1 后有所改善?)
  2. WPF3.5 SP1 以前的 TreeView 是不支持 UIV的。而之后的 TreeView 在默认情况下 UIV 处于关闭状态,需要手动打开。
  3. 实现 UIV 需要一个对应的 ScollViewer。
  4. ScollViewer 中的 CanContentScroll 属性为 True 时,子对象才能实现 UIV。
    该属性为 True 时,ScollViewer 在 Measure 时会把当前的 ViewPort 大小传给 Content 元素。否则,它会把 Infinite 传给 Content。
    同 时,由子元素(也就是 VirtualizingStackPanel)需要实现 IScollInfo 并返回 Scroll 相关信息,而 ScollViewer 则只是一个简单的视窗;这样,子元素就可以在内部实现 UIV,并告知其对应的 ScrollOwner(ScrollViewer) 相关的拖动信息。

所以,上面的 xaml 主要有两个错误:

  1. ScrollViewer.CanContentScroll 应该设置为 True。
  2. 应该把 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

 

欢迎转载,转载请注明:

转载自 胡庆访http://zgynhqf.cnblogs.com/ ]

OpenExpressApp:精通 WPF UI Virtualization的更多相关文章

  1. 精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能)

    原文:精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能) 本篇博客主要说明如何使用 UI Virtualization(以下简称为 UIV) 来提 ...

  2. [WPF]WPF Data Virtualization和UI Virtualization

    这篇博客将介绍WPF中的虚拟化技术. 1. Data Virtualization 通常情况下我们说数据虚拟化是指数据源没有完全加载,仅加载当前需要显示的数据呈现给用户.这种场景会让我们想到数据分页显 ...

  3. C#如何在VS2015 2017版本中编写WPF UI界面引入第三方SVG图形

    原文:C#如何在VS2015 2017版本中编写WPF UI界面引入第三方SVG图形 在VS2015 2017版本中编写WPF UI界面引入第三方SVG图形     最近在写WPF界面的时候遇到一个情 ...

  4. UI-WPF_UI:WPF UI - DMSkin官方网站

    ylbtech-UI-WPF_UI:WPF UI - DMSkin官方网站 1.返回顶部 1. QQ: 944095635 DMSkin.com 首页 论坛 定制 博客  联系   DMSkin 3. ...

  5. WPF UI布局之概述

    在线演示:http://v.youku.com/v_show/id_XNzA5NDk2Mjcy.html 清晰版视频+代码下载:http://115.com/lb/5lbeer0m9lad 一.简单介 ...

  6. WPF UI 开源专贴

    1.ReactiveUI https://github.com/reactiveui/ReactiveUI http://www.reactiveui.net A MVVM framework tha ...

  7. 关闭VS2015的WPF UI调试工具

    VS菜单: 工具 > 选项 > 调试 > 常规 > 启用Xaml 的UI调试工具.把勾勾去掉.

  8. Open-source Tutorial - Material Design for WPF UI

    安装 Material Design Themes 通过 NuGet 包管理器搜索自动安装 通过 NuGet 包管理器控制台手动安装 Install-Package MaterialDesignThe ...

  9. WPF UI虚拟化

    ComboBox

随机推荐

  1. Android 软键盘监听事件

    Android软键盘的隐藏显示研究 Android是一个针对触摸屏专门设计的操作系统,当点击编辑框,系统自动为用户弹出软键盘,以便用户进行输入.     那么,弹出软键盘后必然会造成原有布局高度的减少 ...

  2. 主从同步设置的重要参数log_slave_updates

    说明:最近部署了mysql的集群环境,详细如下M01和M02为主主复制,M01和R01为主从复制:在测试的过程中发现了以下问题: 1.M01和M02的主主复制是没有问题的(从M01写入数据能同步到M0 ...

  3. Tomcat8及之后版本出现的The valid characters are defined in RFC 7230 and RFC 3986

    升级tomcat8及更高版本带来的问题 由于使用get请求,链接中参数携带特殊字符,由于Tomcat的新版本中增加了一个新特性,就是严格按照 RFC 3986规范进行访问 解析,而 RFC 3986规 ...

  4. 手机端自适应布局demo

    原型如下: 要求如下:适应各种机型 源码如下: <!DOCTYPE html > <html>     <head>         <meta http-e ...

  5. FMDB数据库框架

    nFMDB   nFMDB n什么是FMDB pFMDB是iOS平台的SQLite数据库框架 pFMDB以OC的方式封装了SQLite的C语言API p nFMDB的优点 p使用起来更加面向对象,省去 ...

  6. ANR问题分析实例

    ANR监测机制包含三种: Service ANR,前台进程中Service生命周期不能超过20秒,后台进程中Service的生命周期不能超过200秒. 在启动Service时,抛出定时消息SERVIC ...

  7. 自定义view布局过程详解

    布局过程,就是程序在运行时利用布局文件的代码来计算出实际尺寸的过程. 布局分为两个阶段:测量阶段和布局阶段. 测量阶段:从上到下递归地调用每个 View 或者 ViewGroup 的 measure( ...

  8. struts2_11_实现自己的拦截器的定义

    1)登录界面代码: <% //设置session的值keyword为user request.getSession().setAttribute("user", " ...

  9. UML类图几种”关系“的总结

    在UML类图中,常见的有以下几种关系: 泛化(Generalization)(继承),  实现(Realization)(接口实现),组合(Composition),聚合(Aggregation),关 ...

  10. UnitOfWork

    数据访问层之UnitOfWork   接上文 项目架构开发:数据访问层之IQuery 本章我们继续IUnitOfWork的开发,从之前的IRepository接口中就可以看出,我们并没有处理单元事务, ...