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. 照片查看器具有照片集锦,阅读器具有文章/书籍/故事的集锦,而购物应用具有产品集锦。A photo viewer has collections of photos, a reader has collections of articles/books/stories, and a shopping app has collections of products. 本主题介绍可以采取哪些措施来提高应用操作集锦的效率。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. ListView GridView (及其他标准 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 个属性,自描述名称类似于 SelectionCheckMarkVisualEnabledSelectedBackgroundThere 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.

这些技术的示例常见于照片查看应用:即使并不加载和显示所有图像,用户仍可以平移/滚动并与集合交互。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. 如果立即运行应用并在网格视图中快速平移/滚动,你会注意到当每个新项目出现在屏幕上时,首先呈现为深灰色矩形(由于 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. 如果立即运行应用并快速在网格视图中平移/滚动,你看到的行为与使用 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 是一个事件,每当在启动或循环期间需要新项目时,你都可以向 ListView/ GridView 提供项目 (ListViewItem/GridViewItem)。ChoosingItemContainer 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) 允许应用根据要显示的数据项目类型在运行时返回不同的项目模板。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. 因此,在应用中使用项目模板选择器前,你应谨慎考虑你的数据是否适用于项目模板选择器。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.