精通 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 个左右。拖动起来也明显地感觉到流畅了许多。
大功告成!
本文转自BloodyAngel博客园博客,原文链接:http://www.cnblogs.com/zgynhqf/archive/2011/12/12/2284335.html,如需转载请自行联系原作者