WPF 性能优化-列表控件性能

下面记录几种针对大数据列表控件性能的增强特性,WPF所有继承自ItemsControl的控件(列表控件)都支持这些增强特性,包括ListBox、ComboBox、ListView、TreeView以及DataGrid。

一、虚拟化
WPF列表控件所提供的最重要的功能就是UI虚拟化。

UI虚拟化是列表只为可见区域中能显示的项创建容器对象的一种技术,对提升列表控件的性能有着显卓的效果。假设有一个数万条记录的ListBox控件,其可见区域只能展示30条记录仪,此时如果使用虚拟化技术,那么ListBox只需要创建30个ListBoxItem(或多几个已保持良好的滑动性能),如果没有使用虚拟化技术,则需要生成属数万个ListBoxItem ,这显然会占用更多的内存,且影响应用的流畅度。

虚拟化的启用

需要注意的是,支持虚拟化的其实是VirtualizingStackPanel容器,此容器控件除了虚拟化支持外,其他的与StackPanel功能是类似的。

ListBox、ListView和DataGrid默认都使用VirtualizingStackPanel来作为子元素的布局容器,因此这些控件默认情况下就是使用虚拟化的,不需要进行额外的处理
ComboBox默认情况下是使用StackPanel来作为子元素的布局容器的,因此要支持虚拟化,必须明确通过提供新的ItemsPanelTemplate将子元素的布局容器设置为VirtualizingStackPanel
TreeView虽然默认都使用VirtualizingStackPanel来作为子元素的布局容器,但是由于早期的WPF中VirtualizingStackPanel不支持层次化数据,为了向后兼容,默认是禁用了虚拟化的,因此要使用虚拟化,需要通过设置 VirtualizingStackPanel.IsVirtualizing="True" 属性开启
ItemsControl默认情况下会使用VirtualizingStackPanel作为子元素的布局容器
破坏虚拟化的因素

很多时候,在不知不觉中虚拟化已经被破坏掉了,下面是几种破坏虚拟化的常见因素:

将列表控件放入不会试图限制其尺寸的容器中时,列表控件会以完整尺寸渲染自身,导致每个子项在内存中都有自己的控件。例如将ListBox控件放入到ScrollViewer或StackPanel中
改变列表控件的模板并且没有使用ItemsPresenter。ItemsPresenter使用ItemsPanelTemplate中设定的子项容器来布局子项,而ItemsPanelTemplate默认的设定是使用VirtualizingStackPanel,如果破坏了这种关系或自己修改了ItemsPanelTemplate的设置,从而使其不使用VirtualizingStackPanel面板,将会丢失虚拟化特性。
不使用数据绑定,例如通过动态创建ListBoxItem来为ListBox控件添加子项,是不会发生虚拟化的。
二、项容器再循环
列表控件在启用虚拟化的情况下,进行滚动时,默认会创建新展示的子项控件,并销毁离开可视区域的子项控件。可以通过设置VirtualizingStackPanel.VirtualizationMode="Recycling"开启像容器再循环模式,这样列表控件会保留少量的子项控件,在滚动时进行服用,只根据新数据更新其中的内容,而不是每次都创建和销毁。这有助于减少内存使用并提高性能,特别是在滚动大量数据时。

VirtualizingStackPanel.VirtualizationMode的有效值为Standard 和 Recycling,默认情况下为Standard

<ListView VirtualizingStackPanel.VirtualizationMode="Recycling">  
   ......
</ListView> 

三、缓存长度
上文中有提及到,VirtualizingStackPanel会多创建几个超过显示范围的子项,以便在开始滚动时候就可以立即显示这些子项,以此来优化交互。我们可以通过使用VirtualizingStackPanel.CacheLength和VirtualizingStackPanel.CacheLengthUnit来进一步调整数量。

VirtualizingStackPanel.CacheLengthUnit:表示缓存的单位,有效值分别为Item、Page、Pixel,其中Page表示可视窗口所能展示的数量的项,Pixel表示像素,适用于项显示不同大小的图片时。
VirtualizingStackPanel.CacheLength:表示缓存指定单位的数量。
值得注意的是,在滚动时,VirtualizingStackPanel会先将可见的项创建并显示后,再在后台中进行缓存的填充,因此缓存对应用程序流畅度的影响很小。

缓存当前显示项的前一页和后一页

<ListBox VirtualizingStackPanel.CacheLength="1" VirtualizingStackPanel.CacheLengthUnit="Page" ....../>

缓存当前显示项的前100项和后100项

<ListBox VirtualizingStackPanel.CacheLength="100" VirtualizingStackPanel.CacheLengthUnit="Item" ....../>

缓存当前显示项的前100项和后500项

<ListBox VirtualizingStackPanel.CacheLength="100,500" VirtualizingStackPanel.CacheLengthUnit="Item" ....../>

四、滚动设置

1、延迟滚动

默认情况下,当用户在滚动条上拖动滑动块时,会实时刷新列表,为了进一步提高滚动性能,可以通过设置ScrollViewer.IsDeferredScrollingEnabled="True"开启延迟滚动,

只有当用户释放滚动滑块时才进行刷新。

<ListBox ScrollViewer.IsDeferredScrollingEnabled="True" ....../>

这个特性需要根据实际情况使用了,虽然可以提高性能,但是无法实时展示滑动到哪个位置了,而实际开发时,用户更喜欢能实时看到当前滑动块的展示位置。

2、滚动单位
VirtualizingStackPanel默认是基于项进行滚动的,也就是说无论是单击滚动条、滚动箭头还是调用ScrollIntoView()方法,在面板上至少会滚动一个完整项,而无法在滚动时展示某个项的一部分。

可以通过VirtualizingStackPanel.ScrollUnit="Pixel"(默认为Item),将VirtualizingStackPanel的滚动设置为基于像素,可以让滚动更加流畅。

<ListBox VirtualizingStackPanel.ScrollUnit="Pixel" ....../>

 



posted @ 2024-10-12 10:51  【君莫笑】  阅读(58)  评论(0编辑  收藏  举报