A ListView és a GridView teljesítményének optimalizálása

Megjegyzés További részletekért lásd a //build/ munkamenetet, amely jelentősen növeli a teljesítményt, amikor a felhasználók nagy mennyiségű adatot használnak a GridView-ban és a ListView-ban.

Az elemek felhasználói felületi virtualizálásával, elemcsökkentésével és fokozatos frissítésével javíthatja a ListView és a GridView teljesítményét és indítási idejét. Az adatvirtualizálási technikákkal kapcsolatban lásd a WinUI ListView és GridView adatvirtualizálását.

A gyűjtemény teljesítményének két fő tényezője

A gyűjtemények módosítása gyakori forgatókönyv. A fényképmegjelenítők fényképgyűjteményeket, az olvasó cikkek, könyvek vagy történetek gyűjteményét, a bevásárlóalkalmazások pedig termékgyűjteményeket is készítenek. Ez a témakör bemutatja, hogyan teheti hatékonyabbá WinUI-alkalmazását a gyűjtemények módosításakor.

A gyűjtemények teljesítményének két fő tényezője van: az egyik a felhasználói felületi szál elemeinek létrehozására fordított idő; a másik a nyers adatkészlet és az adatok rendereléséhez használt felhasználói felületi elemek által használt memória.

A zökkenőmentes panorámázás és görgetés érdekében elengedhetetlen, hogy a UI szál hatékony és intelligens munkát végez az elemek példányosításával, adatkötésével és elrendezésével.

Felhasználói felület virtualizálása

A felhasználói felület virtualizálása a legfontosabb fejlesztés, amelyet el lehet végezni. Ez azt jelenti, hogy az elemeket képviselő felhasználói felületi elemek igény szerint jönnek létre. Egy 1000 elemből álló gyűjteményhez kötött elemvezérlők esetében erőforrás-pazarlás lenne az összes elem felhasználói felületének egyidejű létrehozása, mert nem jeleníthetők meg egyszerre. A ListView és a GridView (és más standard ItemsControl-származtatott vezérlők) felhasználói felületi virtualizálást végeznek Ön számára. Ha az elemek közel vannak a megtekintéshez (néhány oldalnyira), a keretrendszer létrehozza az elemek felhasználói felületét, és gyorsítótárazza őket. Ha nem valószínű, hogy az elemek ismét megjelennek, a keretrendszer visszanyeri a memóriát.

Ha egyéni elempanelsablont ad meg (lásd : ItemsPanel), győződjön meg arról, hogy virtualizáló panelt használ, például ItemsWrapGrid vagy ItemsStackPanel. Ha a VariableSizedWrapGrid, WrapGrid vagy StackPanel függvényt használja, akkor nem fog virtualizálni. Emellett a következő ListView-események csak az ItemsWrapGrid vagy az ItemsStackPanel használatakor jönnek létre: ChoosingGroupHeaderContainer, ChoosingItemContainer és ContainerContentChanging. A Windows App SDK egyéni elrendezései esetében a modern egyenértékű egy VirtualizingLayout-alapú implementáció, ha a beépített elemek paneljei nem felelnek meg az igényeinek.

A nézetportok fogalma kritikus fontosságú a felhasználói felület virtualizálása szempontjából, mivel a keretrendszernek létre kell hoznia azokat az elemeket, amelyek valószínűleg megjelennek. Az ItemsControl nézetportja általában a logikai vezérlő mértéke. A ListView nézetportja például a ListView elem szélessége és magassága. Egyes panelek korlátlan helyet engednek a gyermekelemeknek, például a ScrollViewernek és az automatikus méretű sorokat vagy oszlopokat tartalmazó rácsnak . Ha egy virtualizált ItemsControl egy ilyen panelen van elhelyezve, elegendő hely kell ahhoz, hogy megjelenítse az összes elemét, ami legyőzi a virtualizálást. Állítsa vissza a virtualizálást az ItemsControl szélességének és magasságának beállításával.

Elemenkénti csökkentés

Az elemek megjelenítéséhez használt felhasználói felületi elemek számának megtartása ésszerű minimálisra.

Amikor először megjelenik egy elemvezérlő, minden elem létrejön, amely ahhoz szükséges, hogy egy nézetablak tele legyen elemekkel. Ahogy az elemek megközelítik a nézetportot, a keretrendszer frissíti a gyorsítótárazott elemsablonok felhasználói felületi elemeit a kötött adatobjektumokkal. A sablonokon belüli korrektúra összetettségének minimalizálása kifizetődő a memóriában és a felhasználói felületi szálon töltött idő alatt, ami különösen pásztázás és görgetés közben javítja a válaszkészséget. A szóban forgó sablonok az elemsablon (lásd : ItemTemplate) és a ListViewItem vagy a GridViewItem vezérlősablonja (az elemvezérlő sablon vagy Az ItemContainerStyle). Az elemszám kis mértékű csökkenésének előnye megszorozva a megjelenített elemek számával.

Például az elemcsökkentésről lásd: XAML-betöltés optimalizálása WinUI-hoz és Windows App SDK-hoz.

A ListViewItem és a GridViewItem alapértelmezett vezérlősablonjai listViewItemPresenter elemet tartalmaznak. Ez a prezentációs elem egy optimalizált egység, amely komplex vizuális elemeket jelenít meg a fókusz, a kijelölés és más vizuális állapotok megjelenítésére. Ha már rendelkezik egyéni elemvezérlő sablonokkal (ItemContainerStyle), vagy ha a jövőben szerkessze egy elemvezérlő sablon másolatát, javasoljuk, hogy a ListViewItemPresentert használja, mert ez az elem optimális egyensúlyt biztosít a teljesítmény és a testreszabhatóság között az esetek többségében. A tulajdonságok beállításával testre szabhatja az előadót. Az alábbi korrektúra például eltávolítja az elem kijelölésekor alapértelmezés szerint megjelenő pipát, és narancssárgára módosítja a kijelölt elem háttérszínét.

...
<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>
<!-- ... -->

A SelectionCheckMarkVisualEnabledhez és a SelectedBackground-hoz hasonló öníró nevek körülbelül 25 tulajdonsággal rendelkeznek. Abban az esetben, ha az előadói típusok nem bizonyulnak elég testreszabhatónak a felhasználási esethez, szerkesztheti helyette a ListViewItemExpanded vagy GridViewItemExpanded vezérlősablon egy másolatát. WinUI-alkalmazásokban tekintse meg azt a generic.xaml fájlt, amely a Windows App SDK-csomaggal együtt jelenik meg az aktuális alapértelmezett sablonokhoz. Vegye figyelembe, hogy ezeknek a sablonoknak a használata némi teljesítmény feláldozását jelenti a testreszabás fokozása érdekében.

ListView- és GridView-elemek fokozatos frissítése

Ha adatvirtualizálást használ, a ListView és a GridView válaszképessége magas maradhat, ha konfigurálja a vezérlőt a még letöltött elemek ideiglenes felhasználói felületi elemeinek megjelenítéséhez. Az ideiglenes elemeket az adatok betöltésekor fokozatosan felváltja a tényleges felhasználói felület.

Emellett – függetlenül attól, hogy honnan tölt be adatokat (helyi lemezről, hálózatról vagy felhőből) – a felhasználók olyan gyorsan pásztázhatják vagy görgethetik a ListView-t vagy a GridView-t , hogy nem lehet minden elemet teljes hűséggel megjeleníteni, miközben megőrzik a zökkenőmentes pásztázást és görgetést. A zökkenőmentes pásztázás és görgetés megőrzése érdekében a helyőrzők használata mellett több fázisban is megjeleníthet egy elemet.

Ezekre a technikákra gyakran láthatunk példát a fényképmegtekintő alkalmazásokban: bár nem minden kép lett betöltve és megjelenítve, a felhasználó továbbra is pásztázhat, görgethet és használhatja a gyűjteményt. Vagy egy filmelem esetében megjelenítheti az első fázis címét, a második fázisban lévő minősítést és a harmadik fázisban lévő plakát képét. A felhasználó a lehető leghamarabb látja az egyes elemek legfontosabb adatait, és ez azt jelenti, hogy képes egyszerre végrehajtani a műveletet. Ezután a kevésbé fontos adatok az idő függvényében lesznek kitöltve. Az alábbi platformfunkciókkal valósíthatja meg ezeket a technikákat.

Helyőrzők

Az ideiglenes helyőrző vizualizációk funkció alapértelmezés szerint be van kapcsolva, és a ShowScrollingPlaceholders tulajdonsággal van vezérelve. Gyors pásztázás és görgetés során ez a funkció vizuális jelzést ad a felhasználónak, hogy több elem még nincs teljesen megjelenítve, miközben megőrzi a gördülékenységet. Ha az alábbi technikák egyikét használja, a ShowScrollingPlaceholders értékét hamis értékre állíthatja, ha nem szeretné, hogy a rendszer renderelje a helyőrzőket.

Progresszív adatsablon frissítése x:Phase használatával

Az x:Phase attribútum továbbra is működik a WinUI-ban, és továbbra is jó módszer az elemtartalom fokozatos megjelenítésére.

Az x:Phase attribútum használata {x:Bind} kötésekkel progresszív adatsablon-frissítések implementálásához.

  1. Így néz ki a kötési forrás (ehhez az adatforráshoz fogunk kapcsolódni).

    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. Itt található a jelölés, amelyet a DeferMainPage.xaml tartalmaz. A rácsnézet tartalmaz egy elemsablont, amely a MyItem osztály Cím, Alcím és Leírás tulajdonságaihoz kötött elemeket tartalmaz. Vegye figyelembe, hogy az x:Fázis alapértelmezett értéke 0. Itt az elemek kezdetben úgy jelennek meg, hogy csak a cím látható. Ezután az alcímelem adathoz van kötve, és láthatóvá válik az összes elem számára, és így tovább, amíg az összes fázis feldolgozásra nem kerül.

    <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. Ha most futtatja az alkalmazást, és elmozgatja vagy gyorsan görget a rácsnézetben, láthatja, hogy amint minden új elem megjelenik a képernyőn, először sötétszürke téglalapként jelenik meg (a ShowsScrollingPlaceholders tulajdonságnak köszönhetően, amely alapértelmezés szerint true), majd megjelenik a cím, ezt követi az alcím, majd a leírás.

Progresszív adatsablon frissítése a ContainerContentChanging használatával

A ContainerContentChanging esemény általános stratégiája, hogy az Átlátszatlanság használatával elrejtse azokat az elemeket, amelyeknek nem kell azonnal láthatónak lenniük. Az elemek újrafeldolgozásakor megőrzik a régi értékeiket, ezért ezeket az elemeket mindaddig el szeretnénk rejteni, amíg nem frissítettük ezeket az értékeket az új adatelemből. Az eseményargumentumok Fázis tulajdonságával határozzuk meg, hogy mely elemeket kell frissíteni és megjeleníteni. Ha további fázisokra van szükség, visszahívást regisztrálunk.

  1. Ugyanazt a kötési forrást fogjuk használni, mint az x:Phase esetében.

  2. Itt található a jelölőnyelv, amelyet a MainPage.xaml tartalmaz. A rácsnézet egy kezelőt deklarál a ContainerContentChanging eseményhez, és tartalmaz egy elemsablont a MyItem osztály Cím, Alcím és Leírás tulajdonságainak megjelenítéséhez használt elemekkel. A ContainerContentChanging maximális teljesítménybeli előnyeinek eléréséhez nem használunk kötéseket a korrektúrában, hanem programozott módon rendeljük hozzá az értékeket. Ez alól kivételt képez a címet megjelenítő elem, amelyet a 0. fázisnak tekintünk.

    <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. Végül pedig a ContainerContentChanging eseménykezelő implementációja . Ez a kód azt is bemutatja, hogyan adunk hozzá egy ExampleItemViewModel típusú tulajdonságot a MainPage-hez , hogy elérhetővé tegyük a kötés forrásosztályát a korrektúralapunkat képviselő osztályból. Ha nem rendelkezik {Binding} kötéssel az adatsablonban, jelölje meg az eseményargumentum-objektumot a kezelő első fázisában kezeltként, hogy arra az elemre utaljon, amelyet nem szükséges adatkörnyezetet beállítania.

    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. Ha most futtatja az alkalmazást, és pásztázik vagy görget gyorsan a rácsnézetben, ugyanazt a viselkedést fogja látni, mint az x:Phase esetében.

Konténer újrahasznosítása heterogén összeállításokkal

Egyes alkalmazásokban különböző felhasználói felülettel kell rendelkeznie a gyűjteményen belüli különböző típusú elemekhez. Ez olyan helyzetet teremthet, amelyben a panelek virtualizálása lehetetlenné teszi az elemek megjelenítésére használt vizuális elemek újrahasznosítását vagy újrafelhasználását. Az elem vizuális elemeinek újraalkotása pásztázás közben elnyomja a virtualizálás nyújtotta teljesítmény előnyét. Egy kis tervezés azonban lehetővé teszi, hogy a virtualizálási panelek újra felhasználják az elemeket. A fejlesztők a forgatókönyvüktől függően több lehetőséget is használhatnak: a SelectItemContainer eseményt vagy egy elemsablon-választót. A ChoosingItemContainer megközelítés jobb teljesítménnyel rendelkezik.

A ChoosingItemContainer esemény

A ChoosingItemContainer olyan esemény, amely lehetővé teszi egy elem (ListViewItem vagy GridViewItem) megadását a ListView vagy a GridView számára, amikor új elemre van szükség az indítás vagy az újrahasznosítás során. Létrehozhat egy tárolót a tároló által megjelenítendő adatelem típusa alapján, ahogyan az az alábbi példában is látható. A ChoosingItemContainer a nagyobb teljesítményű módja a különböző adatsablonok különböző elemekhez való használatának. A tároló gyorsítótárazása a ChoosingItemContainer használatával érhető el. Ha például öt különböző sablonnal rendelkezik, és az egyik sablon nagyságrendekkel gyakrabban fordul elő, mint a többi, akkor a ChoosingItemContainer lehetővé teszi, hogy ne csak a szükséges arányú elemeket hozzon létre, hanem a megfelelő számú elem gyorsítótárazását és újrahasznosításra való rendelkezésre állását is. ChoosingGroupHeaderContainer ugyanazt a funkciót biztosítja a csoportfejlécekhez.

// 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 is not null, 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;
        }
    }
}

Elemsablon-választó

Az elemsablon-választó (DataTemplateSelector) lehetővé teszi, hogy az alkalmazás futásidőben egy másik elemsablont adjon vissza a megjelenítendő adatelem típusától függően. Ez hatékonyabbá teszi a fejlesztést, de megnehezíti a felhasználói felület virtualizálását, mivel nem minden elemsablon használható újra minden adatelemhez.

Egy elem (ListViewItem vagy GridViewItem) újrahasznosításakor a keretrendszernek el kell döntenie, hogy a Lomtár-várólistában használható elemek rendelkeznek-e olyan elemsablonnal, amely megfelel az aktuális adatelem által kívántnak. Ha nincsenek elemek a megfelelő elemsablonnal az újrahasznosítási sorban, akkor létrehozunk egy új elemet, amelyhez a megfelelő elemsablont példányosítjuk. Ha viszont az újrahasznosítási üzenetsor tartalmaz egy, a megfelelő elemsablonnal rendelkező elemet, akkor a rendszer eltávolítja az elemet a lomtári üzenetsorból, és az aktuális adatelemhez használja. Az elemsablon-választó olyan helyzetekben működik, amikor csak kis számú elemsablont használ, és a különböző elemsablonokat használó elemek gyűjteményében egyenletes eloszlás található.   Ha a különböző elemsablonokat használó elemek egyenlőtlen eloszlása van, akkor valószínűleg új elemsablonokat kell létrehozni a pásztázás során, és ez a virtualizálás által nyújtott előnyök nagy részét eltúlozza. Emellett egy elemsablon-választó csak öt lehetséges jelöltet tekint meg annak kiértékelésekor, hogy egy adott tároló újra felhasználható-e az aktuális adatelemhez. Ezért alaposan gondolja át, hogy az adatok megfelelőek-e egy elemsablon-választóhoz, mielőtt használná az egyiket a WinUI-alkalmazásban. Ha a gyűjtemény többnyire homogén, akkor a választó mindig ugyanazt a típust adja vissza. Csak vegye figyelembe, hogy milyen árat kell fizetnie a homogenitás ritka kivételeiért, és fontolja meg, hogy a ChoosingItemContainer vagy két elemvezérlő használata előnyösebb-e.