ListView 與 GridView UI 最佳化ListView and GridView UI optimization

注意  如需詳細資訊,請參閱 //build/ 講座使用者與 GridView 與 ListView 中的大量資料互動時大幅提升效能Note   For more details, see the //build/ session Dramatically Increase Performance when Users Interact with Large Amounts of Data in GridView and ListView.

透過 UI 虛擬化、減少元素以及漸進式更新項目,改善 ListViewGridView 的效能和啟動時間。Improve ListView and GridView performance and startup time through UI virtualization, element reduction, and progressive updating of items. 如需資料虛擬化技術的資訊,請參閱 ListView 和 GridView 資料虛擬化For data virtualization techniques, see ListView and GridView data virtualization.

集合效能的兩個關鍵因素Two key factors in collection performance

操作集合是常見的案例。Manipulating collections is a common scenario. 相片檢視器有相片的集合、閱讀程式有文章/書籍/故事的集合,而購物 app 有產品的集合。A photo viewer has collections of photos, a reader has collections of articles/books/stories, and a shopping app has collections of products. 這個主題說明您可以執行的動作,讓您的 app 在操作集合時更有效率。This topic shows what you can do to make your app efficient at manipulating collections.

與集合有關的效能有兩個關鍵因素:一個是 UI 執行緒建立項目所花的時間,另一個則是原始資料集與用來轉譯該資料的 UI 元素所使用的記憶體。There are two key factors in performance when it comes to collections: one is the time spent by the UI thread creating items; the other is the memory used by both the raw data set and the UI elements used to render that data.

如果想要順暢移動瀏覽/捲動,UI 執行緒必須能夠執行有效且智慧的具現化、資料繫結和配置項目工作。For smooth panning/scrolling, it's vital that the UI thread do an efficient and smart job of instantiating, data-binding, and laying out items.

UI 虛擬化UI virtualization

UI 虛擬化是您可以執行的最重要改善。UI virtualization is the most important improvement you can make. 這意謂著系統會依需求建立代表項目的 UI 元素。This means that UI elements representing the items are created on demand. 對於繫結至 1000 個項目集合的項目控制項,同時針對所有項目建立 UI 是浪費資源,因為項目不會同時全部顯示。For an items control bound to a 1000-item collection, it would be a waste of resources to create the UI for all the items at the same time, because they can't all be displayed at the same time. ListViewGridView (及其他標準 ItemsControl 衍生的控制項) 會為您執行 UI 虛擬化。ListView and GridView (and other standard ItemsControl-derived controls) perform UI virtualization for you. 當項目即將被捲動到檢視中 (相差幾頁) 時,架構會產生項目的 UI 並且快取它們。When items are close to being scrolled into view (a few pages away), the framework generates the UI for the items and caches them. 當不太可能再次顯示那些項目時,架構就會回收記憶體。When it's unlikely that the items will be shown again, the framework re-claims the memory.

如果您提供自訂項目面板範本 (請參閱 ItemsPanel),則確定您使用虛擬面板,例如 ItemsWrapGridItemsStackPanelIf you provide a custom items panel template (see ItemsPanel) then make sure you use a virtualizing panel such as ItemsWrapGrid or ItemsStackPanel. 如果您使用 VariableSizedWrapGridWrapGridStackPanel,則不會虛擬化。If you use VariableSizedWrapGrid, WrapGrid, or StackPanel, then you will not get virtualization. 此外,只有在使用 ItemsWrapGridItemsStackPanel 時,才會引發下列 ListView 事件:ChoosingGroupHeaderContainerChoosingItemContainerContainerContentChangingAdditionally, the following ListView events are raised only when using an ItemsWrapGrid or an ItemsStackPanel: ChoosingGroupHeaderContainer, ChoosingItemContainer, and ContainerContentChanging.

檢視區概念對 UI 虛擬化很重要,因為架構必須建立可能要顯示的元素。The concept of a viewport is critical to UI virtualization because the framework must create the elements that are likely to be shown. 一般而言,ItemsControl 的檢視區是邏輯控制項的延伸。In general, the viewport of an ItemsControl is the extent of the logical control. 例如,ListView 的檢視區是 ListView 元素的寬度和高度。For example, the viewport of a ListView is the width and height of the ListView element. 有些面板允許子元素有不限數量的空間,範例是 ScrollViewerGrid,使用自動調整大小的列或欄。Some panels allow child elements unlimited space, examples being ScrollViewer and a Grid, with auto-sized rows or columns. 當虛擬化的 ItemsControl 放在這類的面板中時,會採用足夠的空間以顯示其所有項目,虛擬化就無效。When a virtualized ItemsControl is placed in a panel like that, it takes enough room to display all of its items, which defeats virtualization. ItemsControl 設定寬度和高度以還原虛擬化。Restore virtualization by setting a width and height on the ItemsControl.

每個項目的元素減少Element reduction per item

將用來轉譯您的項目的 UI 元素數保持在合理的最小值。Keep the number of UI elements used to render your items to a reasonable minimum.

當第一次顯示項目控制項時,會建立用來轉譯充滿項目之檢視區所需的所有元素。When an items control is first shown, all the elements needed to render a viewport full of items are created. 此外,當項目接近檢視區,架構會使用繫結資料物件更新快取項目範本中的 UI 元素。Also, as items approach the viewport, the framework updates the UI elements in cached item templates with the bound data objects. 最小化範本內標記的複雜度可以換得記憶體和 UI 執行緒花費的時間,特別是在移動瀏覽/捲動時能夠改善回應性。Minimizing the complexity of the markup inside templates pays off in memory and in time spent on the UI thread, improving responsiveness especially while panning/scrolling. 問題中的範本是項目範本 (請參閱 ItemTemplate) 與 ListViewItemGridViewItem (項目控制項範本或 ItemContainerStyle) 的控制項範本。The templates in question are the item template (see ItemTemplate) and the control template of a ListViewItem or a GridViewItem (the item control template, or ItemContainerStyle). 即使是小型元素計數減少乘上顯示項目的好處。The benefit of even a small reduction in element count is multiplied by the number of items displayed.

如需元素減少的範例,請參閱最佳化您的 XAML 標記For examples of element reduction, see Optimize your XAML markup.

ListViewItemGridViewItem 的預設控制項範本包含 ListViewItemPresenter 元素。The default control templates for ListViewItem and GridViewItem contain a ListViewItemPresenter element. 這個展示器是單一的最佳化元素,顯示焦點、選擇和其他視覺狀態的複雜視覺效果。This presenter is a single optimized element that displays complex visuals for focus, selection, and other visual states. 如果您已經有自訂項目控制項範本 (ItemContainerStyle),或如果您在未來會編輯項目控制項範本的複本,我們建議您使用 ListViewItemPresenter,因為在大部分情況下,這些元素會提供您效能和自訂之間的最佳平衡。If you already have custom item control templates (ItemContainerStyle), or if in future you edit a copy of an item control template, then we recommend you use a ListViewItemPresenter because that element will give you optimum balance between performance and customizability in the majority of cases. 您可以在展示器上設定屬性以自訂。You customize the presenter by setting properties on it. 例如,以下標記移除選取項目時預設顯示的核取記號,並將所選項目的背景色彩變更為橘色。For example, here's markup that removes the check mark that appears by default when an item is selected, and changes the background color of the selected item to orange.

...
<ListView>
 ...
 <ListView.ItemContainerStyle>
 <Style TargetType="ListViewItem">
 <Setter Property="Template">
 <Setter.Value>
 <ControlTemplate TargetType="ListViewItem">
 <ListViewItemPresenter SelectionCheckMarkVisualEnabled="False" SelectedBackground="Orange"/>
 </ControlTemplate>
 </Setter.Value>
 </Setter>
 </Style>
 </ListView.ItemContainerStyle>
</ListView>
<!-- ... -->

有大約 25 個屬性含有類似於 SelectionCheckMarkVisualEnabledSelectedBackground 的自我描述名稱。There are about 25 properties with self-describing names similar to SelectionCheckMarkVisualEnabled and SelectedBackground. 如果展示器類型證明不足以針對您的使用案例進行自訂,您可以改為編輯 ListViewItemExpandedGridViewItemExpanded 控制項範本的複本。Should the presenter types prove not to be customizable enough for your use case, you can edit a copy of the ListViewItemExpanded or GridViewItemExpanded control template instead. 您可以在 \Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<version>\Generic\generic.xaml 中找到這些項目。These can be found in \Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<version>\Generic\generic.xaml. 請注意,使用這些範本表示,增加自訂就要犧牲一些效能。Be aware that using these templates means trading some performance for the increase in customization.

漸進式更新 ListView 與 GridView 項目Update ListView and GridView items progressively

如果您使用資料虛擬化,則可以保留 ListViewGridView 的高回應性,方法是設定控制項針對仍在 (下載) 載入的項目轉譯暫時 UI 元素。If you're using data virtualization then you can keep ListView and GridView responsiveness high by configuring the control to render temporary UI elements for the items still being (down)loaded. 隨著資料載入,暫時元素隨後會逐漸被實際 UI 取代。The temporary elements are then progressively replaced with actual UI as data loads.

而且—不論您是從哪裡載入資料 (本機磁碟機、網路或雲端)—使用者可以快速移動瀏覽/捲動 ListViewGridView,以致於無法在保留順暢移動瀏覽/捲動的同時,完全不失真地轉譯每個項目。Also—no matter where you're loading data from (local disk, network, or cloud)—a user can pan/scroll a ListView or GridView so rapidly that it's not possible to render each item with full fidelity while preserving smooth panning/scrolling. 若要保留順暢的移動瀏覽/捲動,您可以選擇在使用預留位置之外,在多個階段中轉譯項目。To preserve smooth panning/scrolling you can choose to render an item in multiple phases in addition to using placeholders.

這些技術的範例常見於相片檢視 app:即使尚未下載和顯示所有影像,使用者仍然可以移動瀏覽/捲動並與集合互動。An example of these techniques is often seen in photo-viewing apps: even though not all of the images have been loaded and displayed, the user can still pan/scroll and interact with the collection. 或者,對於「影片」項目,您可以在第一個階段中顯示標題,在第二個階段顯示分級,以及在第三個階段顯示海報影像。Or, for a "movie" item, you could show the title in the first phase, the rating in the second phase, and an image of the poster in the third phase. 使用者能夠越早看到每個項目的最重要資料,就表示他們能夠越快採取動作。The user sees the most important data about each item as early as possible, and that means they're able to take action at once. 然後在時間允許時會填入較不重要的資訊。Then the less important info is filled-in as time allows. 以下是您可以用來實作這些技術的平台功能。Here are the platform features you can use to implement these techniques.

預留位置Placeholders

暫時預留位置視覺效果功能預設為開啟,它是使用 ShowsScrollingPlaceholders 屬性進行控制。The temporary placeholder visuals feature is on by default, and it's controlled with the ShowsScrollingPlaceholders property. 在快速移動瀏覽/捲動期間,這項功能可為使用者提供視覺提示,以了解還有更多項目尚未完整顯示,同時保留順暢度。During fast panning/scrolling, this feature gives the user a visual hint that there are more items yet to fully display while also preserving smoothness. 如果您使用下列其中一個技術,若您不想讓系統轉譯預留位置,則可將 ShowsScrollingPlaceholders 設為 False。If you use one of the techniques below then you can set ShowsScrollingPlaceholders to false if you prefer not to have the system render placeholders.

使用 x:Phase 的漸進式資料範本更新Progressive data template updates using x:Phase

以下說明如何使用 x:Phase 屬性{x:Bind} 繫結,實作漸進式資料範本更新。Here's how to use the x:Phase attribute with {x:Bind} bindings to implement progressive data template updates.

  1. 以下是繫結來源的外觀 (這是我們將繫結至的資料來源)。Here's what the binding source looks like (this is the data source that we'll bind to).

    namespace LotsOfItems
    {
        public class ExampleItem
        {
            public string Title { get; set; }
            public string Subtitle { get; set; }
            public string Description { get; set; }
        }
    
        public class ExampleItemViewModel
        {
            private ObservableCollection<ExampleItem> exampleItems = new ObservableCollection<ExampleItem>();
            public ObservableCollection<ExampleItem> ExampleItems { get { return this.exampleItems; } }
    
            public ExampleItemViewModel()
            {
                for (int i = 1; i < 150000; i++)
                {
                    this.exampleItems.Add(new ExampleItem(){
                        Title = "Title: " + i.ToString(),
                        Subtitle = "Sub: " + i.ToString(),
                        Description = "Desc: " + i.ToString()
                    });
                }
            }
        }
    }
    
  2. 以下顯示 DeferMainPage.xaml 包含的標記。Here's the markup that DeferMainPage.xaml contains. 格線檢視包含項目範本,具有繫結至 MyItem 類別的 TitleSubtitleDescription 屬性的元素。The grid view contains an item template with elements bound to the Title, Subtitle, and Description properties of the MyItem class. 請注意,x:Phase 預設值為 0。Note that x:Phase defaults to 0. 這裡項目僅以可見的標題進行初始轉譯。Here, items will be initially rendered with just the title visible. 然後副標題元素會資料繫結並且對所有項目顯示,直到所有階段都已處理。Then the subtitle element will be data bound and made visible for all the items and so on until all the phases have been processed.

    <Page
        x:Class="LotsOfItems.DeferMainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:lotsOfItems="using:LotsOfItems"
        mc:Ignorable="d">
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <GridView ItemsSource="{x:Bind ViewModel.ExampleItems}">
                <GridView.ItemTemplate>
                    <DataTemplate x:DataType="lotsOfItems:ExampleItem">
                        <StackPanel Height="100" Width="100" Background="OrangeRed">
                            <TextBlock Text="{x:Bind Title}"/>
                            <TextBlock Text="{x:Bind Subtitle}" x:Phase="1"/>
                            <TextBlock Text="{x:Bind Description}" x:Phase="2"/>
                        </StackPanel>
                    </DataTemplate>
                </GridView.ItemTemplate>
            </GridView>
        </Grid>
    </Page>
    
  3. 如果您現在執行 app 並且快速移動瀏覽/捲動格線檢視,則您會發現畫面上出現的每個新項目,一開始轉譯成暗灰色矩形 (由於 ShowsScrollingPlaceholders 屬性預設為 true),然後標題會出現,後面跟著副標題,再來是描述。If you run the app now and pan/scroll quickly through the grid view then you'll notice that as each new item appears on the screen, at first it is rendered as a dark gray rectangle (thanks to the ShowsScrollingPlaceholders property defaulting to true), then the title appears, followed by subtitle, followed by description.

使用 ContainerContentChanging 的漸進式資料範本更新Progressive data template updates using ContainerContentChanging

ContainerContentChanging 事件的一般策略是使用 Opacity 來隱藏不需要立即看到的元素。The general strategy for the ContainerContentChanging event is to use Opacity to hide elements that don’t need to be immediately visible. 回收元素時,它們會保留舊值,所以我們想要隱藏這些元素直到我們已經從新的資料項目更新這些值。When elements are recycled, they will retain their old values so we want to hide those elements until we've updated those values from the new data item. 我們在事件引數上使用 Phase 屬性,以判斷要更新和顯示的項目。We use the Phase property on the event arguments to determine which elements to update and show. 如果需要額外的階段,我們會註冊回呼。If additional phases are needed, we register a callback.

  1. 我們會針對 x:Phase 使用相同的繫結來源。We'll use the same binding source as for x:Phase.

  2. 以下顯示 MainPage.xaml 包含的標記。Here's the markup that MainPage.xaml contains. 格線檢視宣告處理常式為其 ContainerContentChanging 事件,所包含的項目範本具有用來顯示 MyItem 類別之 TitleSubtitleDescription 屬性的元素。The grid view declares a handler to its ContainerContentChanging event, and it contains an item template with elements used to display the Title, Subtitle, and Description properties of the MyItem class. 為了獲得使用 ContainerContentChanging 的最大效能優點,我們不在標記中使用繫結,而是改為以程式設計的方式指派值。To get the maximum performance benefits of using ContainerContentChanging, we don't use bindings in the markup but we instead assign values programmatically. 以下的例外狀況是我們在階段 0 中考量,顯示標題的元素。The exception here is the element displaying the title, which we consider to be in phase 0.

    <Page
        x:Class="LotsOfItems.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:lotsOfItems="using:LotsOfItems"
        mc:Ignorable="d">
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <GridView ItemsSource="{x:Bind ViewModel.ExampleItems}" ContainerContentChanging="GridView_ContainerContentChanging">
                <GridView.ItemTemplate>
                    <DataTemplate x:DataType="lotsOfItems:ExampleItem">
                        <StackPanel Height="100" Width="100" Background="OrangeRed">
                            <TextBlock Text="{x:Bind Title}"/>
                            <TextBlock Opacity="0"/>
                            <TextBlock Opacity="0"/>
                        </StackPanel>
                    </DataTemplate>
                </GridView.ItemTemplate>
            </GridView>
        </Grid>
    </Page>
    
  3. 最後,這是 ContainerContentChanging 事件處理常式的實作。Lastly, here's the implementation of the ContainerContentChanging event handler. 這個程式碼也說明我們如何將類型 RecordingViewModel 的屬性新增至 MainPage,以公開類別的繫結來源類別,該類別代表我們的標記頁面。This code also shows how we add a property of type RecordingViewModel to MainPage to expose the binding source class from the class that represents our page of markup. 只要您在資料範本中沒有任何 {Binding} 繫結,則請在處理常式的第一個階段將事件引數物件標示為已處理,提示該項目不需要設定資料內容。As long as you don't have any {Binding} bindings in your data template, then mark the event arguments object as handled in the first phase of the handler to hint to the item that it needn't set a data context.

    namespace LotsOfItems
    {
        /// <summary>
        /// An empty page that can be used on its own or navigated to within a Frame.
        /// </summary>
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
                this.ViewModel = new ExampleItemViewModel();
            }
    
            public ExampleItemViewModel ViewModel { get; set; }
    
            // Display each item incrementally to improve performance.
            private void GridView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
            {
                if (args.Phase != 0)
                {
                    throw new System.Exception("We should be in phase 0, but we are not.");
                }
    
                // It's phase 0, so this item's title will already be bound and displayed.
    
                args.RegisterUpdateCallback(this.ShowSubtitle);
    
                args.Handled = true;
            }
    
            private void ShowSubtitle(ListViewBase sender, ContainerContentChangingEventArgs args)
            {
                if (args.Phase != 1)
                {
                    throw new System.Exception("We should be in phase 1, but we are not.");
                }
    
                // It's phase 1, so show this item's subtitle.
                var templateRoot = args.ItemContainer.ContentTemplateRoot as StackPanel;
                var textBlock = templateRoot.Children[1] as TextBlock;
                textBlock.Text = (args.Item as ExampleItem).Subtitle;
                textBlock.Opacity = 1;
    
                args.RegisterUpdateCallback(this.ShowDescription);
            }
    
            private void ShowDescription(ListViewBase sender, ContainerContentChangingEventArgs args)
            {
                if (args.Phase != 2)
                {
                    throw new System.Exception("We should be in phase 2, but we are not.");
                }
    
                // It's phase 2, so show this item's description.
                var templateRoot = args.ItemContainer.ContentTemplateRoot as StackPanel;
                var textBlock = templateRoot.Children[2] as TextBlock;
                textBlock.Text = (args.Item as ExampleItem).Description;
                textBlock.Opacity = 1;
            }
        }
    }
    
  4. 如果您現在執行 app 並且快速移動瀏覽/捲動格線檢視,則您會看到與使用 x:Phase 時相同的行為。If you run the app now and pan/scroll quickly through the grid view then you'll see the same behavior as for as for x:Phase.

含異質集合的容器回收Container-recycling with heterogeneous collections

在某些應用程式中,在集合內需要針對不同類型的項目有不同的 UI。In some applications, you need to have different UI for different types of item within a collection. 這會產生一種虛擬化面板不可能發生的情況,也就是重複使用/回收使用過的元素來顯示項目。This can create a situation where it is impossible for virtualizing panels to reuse/recycle the visual elements used to display the items. 在移動瀏覽期間重新建立項目的視覺元素,會消除許多虛擬化提供的效能優點。Recreating the visual elements for an item during panning undoes many of the performance wins provided by virtualization. 不過,稍加規劃就可允許虛擬化面板重複使用元素。However, a little planning can allow virtualizing panels to reuse the elements. 開發人員視其案例會有數個選項:ChoosingItemContainer 事件或項目範本選取器。Developers have a couple of options depending on their scenario: the ChoosingItemContainer event, or an item template selector. ChoosingItemContainer 方法有較佳的效能。The ChoosingItemContainer approach has better performance.

ChoosingItemContainer 事件The ChoosingItemContainer event

ChoosingItemContainer 事件可讓您在每當啟動或回收期間需要新項目時就提供項目 (ListViewItem/GridViewItem) 給 ListView/GridViewChoosingItemContainer is an event that allows you to provide an item (ListViewItem/GridViewItem) to the ListView/GridView whenever a new item is needed during start-up or recycling. 您可以根據容器將會顯示的資料項目類型建立容器 (如下列範例所示)。You can create a container based on the type of data item the container will display (shown in the example below). ChoosingItemContainer 是針對不同項目使用不同資料範本,一個高效能的方式。ChoosingItemContainer is the higher-performing way to use different data templates for different items. 容器快取可以使用 ChoosingItemContainer 來達成。Container caching is something that can be achieved using ChoosingItemContainer. 例如,如果您有五個不同的範本,其中某個範本比其他範本更常發生,則 ChoosingItemContainer 不僅可讓您以需要的比例建立項目,還可保留適當的快取元素數目以供回收。For example, if you have five different templates, with one template occurring an order of magnitude more often than the others, then ChoosingItemContainer allows you not only to create items at the ratios needed but also to keep an appropriate number of elements cached and available for recycling. ChoosingGroupHeaderContainer 為群組標頭提供相同的功能。ChoosingGroupHeaderContainer provides the same functionality for group headers.

// Example shows how to use ChoosingItemContainer to return the correct
// DataTemplate when one is available. This example shows how to return different 
// data templates based on the type of FileItem. Available ListViewItems are kept
// in two separate lists based on the type of DataTemplate needed.
private void ListView_ChoosingItemContainer
    (ListViewBase sender, ChoosingItemContainerEventArgs args)
{
    // Determines type of FileItem from the item passed in.
    bool special = args.Item is DifferentFileItem;

    // Uses the Tag property to keep track of whether a particular ListViewItem's 
    // datatemplate should be a simple or a special one.
    string tag = special ? "specialFiles" : "simpleFiles";

    // Based on the type of datatemplate needed return the correct list of 
    // ListViewItems, this could have also been handled with a hash table. These 
    // two lists are being used to keep track of ItemContainers that can be reused.
    List<UIElement> relevantStorage = special ? specialFileItemTrees : simpleFileItemTrees;

    // args.ItemContainer is used to indicate whether the ListView is proposing an 
    // ItemContainer (ListViewItem) to use. If args.Itemcontainer, then there was a 
    // recycled ItemContainer available to be reused.
    if (args.ItemContainer != null)
    {
        // The Tag is being used to determine whether this is a special file or 
        // a simple file.
        if (args.ItemContainer.Tag.Equals(tag))
        {
            // Great: the system suggested a container that is actually going to 
            // work well.
        }
        else
        {
            // the ItemContainer's datatemplate does not match the needed 
            // datatemplate.
            args.ItemContainer = null;
        }
    }

    if (args.ItemContainer == null)
    {
        // see if we can fetch from the correct list.
        if (relevantStorage.Count > 0)
        {
            args.ItemContainer = relevantStorage[0] as SelectorItem;
        }
        else
        {
            // there aren't any (recycled) ItemContainers available. So a new one 
            // needs to be created.
            ListViewItem item = new ListViewItem();
            item.ContentTemplate = this.Resources[tag] as DataTemplate;
            item.Tag = tag;
            args.ItemContainer = item;
        }
    }
}

項目範本選取器Item template selector

項目範本選取器 (DataTemplateSelector) 可讓 app 根據要顯示的資料項目類型,在執行階段傳回不同的項目範本。An item template selector (DataTemplateSelector) allows an app to return a different item template at runtime based on the type of the data item that will be displayed. 這可以讓開發更具生產力,但因為不是每個項目範本都可以針對每個資料項目重複使用,這也會讓 UI 虛擬化更困難。This makes development more productive, but it makes UI virtualization more difficult because not every item template can be reused for every data item.

回收項目 (ListViewItem/GridViewItem) 時,架構必須決定回收佇列 (回收佇列會快取目前沒有用來顯示資料的項目) 中可供使用的項目是否有項目範本符合目前資料項目所需的項目範本。When recycling an item (ListViewItem/GridViewItem), the framework must decide whether the items that are available for use in the recycle queue (the recycle queue is a cache of items that are not currently being used to display data) have an item template that will match the one desired by the current data item. 如果回收佇列中沒有任何項目有適當的項目範本,則會建立新的項目,並且會針對這個項目具現化適當的項目範本。If there are no items in the recycle queue with the appropriate item template then a new item is created, and the appropriate item template is instantiated for it. 但是如果回收佇列中的項目有適當的項目範本,則該項目會從回收佇列中移除,並用於目前的資料項目。If, on other hand, the recycle queue contains an item with the appropriate item template then that item is removed from the recycle queue and is used for the current data item. 項目範本選取器只能在使用少量項目範本的情形中運作,在使用不同項目範本的項目集合中具有平坦的分佈。An item template selector works in situations where only a small number of item templates are used and there is a flat distribution throughout the collection of items that use different item templates.

如果使用不同項目範本的項目有不平坦的分佈,很可能需要在移動瀏覽時建立新的項目範本,這會讓虛擬化提供的許多好處無效。When there is an uneven distribution of items that use different item templates then new item templates will likely need to be created during panning, and this negates many of the gains provided by virtualization. 此外,項目範本選取器在評估特定容器是否可供目前的資料項目重複使用時,只考量五個可能的候選項目。Additionally, an item template selector only considers five possible candidates when evaluating whether a particular container can be reused for the current data item. 因此您在 app 中使用項目範本選取器之前,應該仔細考量您的資料是否適合。So you should carefully consider whether your data is appropriate for use with an item template selector before using one in your app. 如果您的集合大部分是同質的,則選取器大部分 (可能幾乎所有) 時間都會傳回相同的類型。If your collection is mostly homogeneous then the selector is returning the same type most (possibly all) of the time. 需要注意的是您對該同質化的極少數例外狀況要付出的代價,並考量使用 ChoosingItemContainer (或兩個項目控制項) 是否會更好。Just be aware of the price you're paying for the rare exceptions to that homegeneity, and consider whether using ChoosingItemContainer (or two items controls) is preferable.