WPF 效能最佳化-列表控制元件效能

【君莫笑】發表於2024-10-12

下面記錄幾種針對大資料列表控制元件效能的增強特性,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" ....../>



相關文章