您的位置:首页 > 产品设计 > UI/UE

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

2012-02-10 15:26 281 查看
本篇博客主要说明如何使用UIVirtualization(以下简称为UIV)来提升OEA框架中TreeGrid控件的性能,同时,给出了一些学习UIV的资源。

问题

最近对OEA的TreeGrid控件进行了比较大的改造,并使用新的控件来替换了系统中所有的DataGrid控件。新的TreeGrid控件实现了很多新的功能,(之后会写一篇文章说明),但是最后遗留了一个问题:由于使用它替换了原来的DataGrid,而DataGrid默认是支持UIVirtualization的,当有些界面的数据量比较大时,没有支持UIV的TreeGrid控件就显得有些力不从心了。为了解决这个问题,这两天看了许多文章并学习了WPF中UIV的知识,在最后终于解决了,待写下此文予以记录。

先来看看实现UIV前:





518条数据,生成了18130个Visuals。

其实,在解决完后看来,问题主要出在TreeGrid的Template上,直接贴上来给大家看看:

<ScrollViewerStyle="{StaticResourceGridTreeViewScroll}"Background="{TemplateBindingBackground}"
Focusable="false"
CanContentScroll="false"
HorizontalScrollBarVisibility="{TemplateBindingScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBindingScrollViewer.VerticalScrollBarVisibility}"
Padding="{TemplateBindingPadding}"
SnapsToDevicePixels="{TemplateBindingSnapsToDevicePixels}">
<Grid>
<ItemsPresenterSnapsToDevicePixels="{TemplateBindingUIElement.SnapsToDevicePixels}"/>
<TextBlockOpacity="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>
<BindingPath="Data"RelativeSource="{RelativeSourceFindAncestor,AncestorType={x:Typeoea:GridTreeView}}"/>
<BindingPath="Items.Count"RelativeSource="{RelativeSourceFindAncestor,AncestorType={x:Typeoea:GridTreeView}}"/>
</MultiBinding>
</TextBlock.Visibility>
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransformScaleX="1.5"/>
<SkewTransformAngleX="-30"/>
<RotateTransformAngle="-30"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</ScrollViewer>


其中,为了实现在列表没有数据时,显示“没有数据”四个字,使用了一个Grid包含了一个ItemsPresenter以及一个TextBlock。这段代码看上去没有什么问题,所以搞了很久都没有把UIV调试出来,最终只有在网上耐心学习了很我UIV的相关知识。

解决方案

其实,相关的UIV知识点有那么几个:

WPF中的VirtualizingStackPanel只支持一层数据的UIV。(这一点好像在WPF3.5SP1后有所改善?)

WPF3.5SP1以前的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>
<ScrollViewerStyle="{StaticResourceGridTreeViewScroll}"Background="{TemplateBindingBackground}"
Focusable="false"
CanContentScroll="{TemplateBindingScrollViewer.CanContentScroll}"
HorizontalScrollBarVisibility="{TemplateBindingScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBindingScrollViewer.VerticalScrollBarVisibility}"
Padding="{TemplateBindingPadding}"
SnapsToDevicePixels="{TemplateBindingSnapsToDevicePixels}">
<VirtualizingStackPanelIsItemsHost="True"SnapsToDevicePixels="{TemplateBindingUIElement.SnapsToDevicePixels}"/>
</ScrollViewer>
<TextBlockOpacity="0.5"TextWrapping="Wrap"FontSize="36"Text="没有数据">……</TextBlock>
</Grid>


同时,注意打开TreeView的UIV支持:

publicclassGridTreeView:TreeView
{
staticGridTreeView()
{
VirtualizingStackPanel.IsVirtualizingProperty.OverrideMetadata(typeof(GridTreeView),newFrameworkPropertyMetadata(true));
}


来看看优化后的结果:





Visuals的数量由1W8降到了3000,当行数更多时,也就保持初始生成3000个左右。拖动起来也明显地感觉到流畅了许多。

大功告成!

相关资源

一篇通俗易懂的UIV概念文章:《UIVirtualization》,其中讲到了WPF及SilverLight中的UIV。(它还有后续的文章:《Datavirtualization》,也很不错)。

之前系统中用到的DataGrid控件,一旦数据被分组之后,性能异常低下。原因其实也和UIV有关:

目前WPF中的控件在Group分组后是不支持UIVirtualization的,原因是当ScrollViewer.CanContentScroll设置为true时,模式由ScrollByPixel变为ScrollbyItem。而分组后的控件中每一个组GroupItem其实就是一个Item,这时,如果继续使用ScrollbyItem模式,将会得到非常差的用户体验,所以MS决定不支持分组后的UIV,ListBox控件的默认模板中有一个Trigger当IsGrouping为True时,设置ConContentScroll为False。相关的内容参见:《UIVirtualization》。其它与分组相关的UIV文章如下:

WPFDataGridVirtualizationwithGrouping》、《MSDNSampleCode:GroupingandVirtualization》、《Problem:ListViewVirtualization》

VirtualizingTreeViewItem》:其中的最佳答案说到几个知识点:VirtualizingStackPanel需要和ScrollViewer进行交互,同时,它只支持一层的Virtualization。可以考虑变通地使用ListBox/ListView来实现假的TreeView,这样就可以实现整个列表的虚拟化。

WPF-VirtualizinganItemsControl》:文中指出,ItemsControl默认不支持UIVirtualization,原因是它的模板中没有一个ScrollViewer。

《ArethereanytricksthatwillhelpmeimproveTreeView’sperformance》:这个系列的文章一共3篇:《PartI》、《PartII》、《PartIII》,最后一篇说明了在如何使用ListBox模拟一个TreeView,这样,由于ListBox本身支持UIVirtualization,所以最后的“TreeView”也就支持了UIVirtualization。类似的控件已经有人传到了CodeProject上:《VirtualizingTreeView(VTreeView)》,其中还正好谈到了上面的这系列文章,非常凑巧的是,它还谈到了CodeProject上被我们系统选择来实现TreeGrid控件的资源:《AVersatileTreeViewforWPF》。

更高级的自定义UIVirtualization,可以先参考以下几篇文章,很不错:《VirtualizingWrapPanel》、《ImplementingavirtualizedpanelinWPF(Avalon)》、《IScrollInfoinAvalonpartI》、《IScrollInfoinAvalonpartII》、《IScrollInfoinAvalonpartIII》。

MS自己的相关资源:

MSDNControlPerformance》、《Howto:FindaTreeViewIteminaTreeView》(如何在UIV的情况下找到控件)、《ChangingselectioninavirtualizedTreeView》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐