2 Stimmen

Wie kann ich die verbindliche Update-Performance einer Listbox in WPF verbessern?

Also hier ist das Szenario, ich habe eine Operation, die eine ObservableCollection löscht, führt eine Abfrage, und füllt die Sammlung mit den Abfrageergebnissen. Diese Sammlung ist databound zu durch eine Listbox. Hier ist der Kicker, 500 Abfrageergebnisse verursacht einige ernsthafte Update-Zeit, die die Benutzeroberfläche für das, was als "zu lange" (in Wirklichkeit ist es .5-2 Sekunden auf den meisten Systemen) wahrgenommen wird blockiert.

Wenn ich einige Tests durchführe, erhalte ich eine deutlich bessere Leistung, wenn ich die Elementvorlage aus der Listbox entferne (duh), aber nicht so viel besser, dass sie sogar die Erwartungen erfüllen würde, die mir mitgeteilt wurden. Ich habe die Bindungen so aktualisiert, dass sie nur in einer Richtung funktionieren, ich habe den Virtualisierungsmodus auf Recycling umgestellt und ich habe sichergestellt, dass ich statische Ressourcen verwende, aber keine der oben genannten Maßnahmen hatte einen spürbaren Einfluss auf das erneute Zeichnen. Ich habe mich gefragt, ob jemand eine gute Idee hat, wie man die Leistung einer Listbox verbessern kann, die mit großen Mengen von Elementen gefüllt ist?

<ListBox x:Name="listBox" Grid.Row="1" Grid.ColumnSpan="5" ItemsSource="{Binding SerialResults}" ItemTemplate="{StaticResource UnitHistoryTemplate}" BorderThickness="2" Grid.IsSharedSizeScope="True" VirtualizingStackPanel.VirtualizationMode="Recycling">

<DataTemplate x:Key="UnitHistoryTemplate">
        <DataTemplate.Resources>
            <DataTemplate x:Key="UnitFailureItemTemplate">
                <Grid>
                    <TextBlock Margin="4,0,4,0" TextWrapping="Wrap" Text="{Binding}" Foreground="Red"/>
                </Grid>
            </DataTemplate>
        </DataTemplate.Resources>
        <Grid d:DesignWidth="580" d:DesignHeight="30" Background="#00000000">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="load"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="run"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="ser"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="mot"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="result"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBlock x:Name="Load" Text="{Binding Load.LoadNumber, Mode=OneTime}" Margin="4,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" d:LayoutOverrides="HorizontalAlignment"/>
            <TextBlock x:Name="Run" Text="{Binding Run.RunNumber, Mode=OneTime}" Margin="4,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" d:LayoutOverrides="HorizontalAlignment" Grid.Column="1"/>
            <TextBlock x:Name="Serial" Text="{Binding Unit.SerialNumber, Mode=OneTime}" Margin="4,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" d:LayoutOverrides="HorizontalAlignment" Grid.Column="2"/>
            <TextBlock x:Name="Mot" Text="{Binding Unit.MotString, Mode=OneTime}" Margin="4,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" d:LayoutOverrides="HorizontalAlignment" Grid.Column="3"/>
            <TextBlock x:Name="Result" Text="{Binding Run.Result, Mode=OneTime}" Margin="4,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="4" d:LayoutOverrides="HorizontalAlignment, GridBox"/>
            <ItemsControl ItemsSource="{Binding Unit.Failed, Mode=OneTime}" ItemTemplate="{StaticResource UnitFailureItemTemplate}" HorizontalAlignment="Left" Margin="4,0,0,0" Grid.Column="5">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </Grid>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding Run.Result, Mode=OneTime}" Value="Aborted">
                <DataTrigger.Setters>
                    <Setter TargetName="Result" Property="Foreground" Value="Red"/>
                </DataTrigger.Setters>
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

Und da es über ein paar Mal unten gefragt wurde, nein, es ist nicht meine beobachtbare Sammlung... Ich unterdrücke das Ereignis, indem ich einen Stapel von Updates und neu zuweisen die Elemente Quelle zu einer neuen Instanz.

    public async void FindUnitHistory()
    {
        if (IsCaching)
        {
            return;
        }
        else if (_serialSearch.Length <= MIN_SEARCH_LENGTH)
        {
            SerialResults.Clear();
            return;
        }

        var newData = new ObservableCollection<UnitHistory>();

        await TaskEx.Run(() =>
            {
                var results = from load in cache.LoadData.AsParallel()
                              from run in load.Runs
                              from unit in run.Units
                              where unit.SerialNumber.StartsWith(_serialSearch)
                              orderby run.RunNumber ascending
                              orderby load.LoadNumber descending
                              select new UnitHistory(load, run, unit);
                foreach (var p in results)
                {
                    newData.Add(p);
                }
            });

        SerialResults = newData;
    }

2voto

Nawaz Punkte 339767

Wenn Sie die Ergebnisse (Ihrer Abfrage) anzeigen möchten, die zufällig eine große Anzahl von Daten sind, dann würde ich Ihnen vorschlagen, DataPager mit DataGrid/ListView/ListBox zu verwenden und nur so viele Elemente anzuzeigen, wie der verfügbare Platz aufnehmen kann (ohne vertikale Bildlaufleiste zu erstellen). Dafür müssen Sie ein DataPager-Steuerelement zu schreiben, wie es nicht mit WPF kommen (obwohl Silverlight DataPager hat!). (Ich habe DataPager für mein Projekt geschrieben, um ein ähnliches Problem zu lösen. Es macht Spaß, seine eigenen Steuerelemente zu schreiben!)

Aber auch vorher können Sie dies versuchen: (es Mai Arbeit)

<ItemsPanelTemplate>
         <VirtualizingStackPanel Orientation="Horizontal"/>
 </ItemsPanelTemplate>

Stattdessen:

<ItemsPanelTemplate>
         <StackPanel Orientation="Horizontal"/>
 </ItemsPanelTemplate>

Sie können auch einen Blick auf diese Artikel werfen:

Virtualisierung von Daten
UI-Virtualisierung
Wie kann ich datenvirtualisierte Elemente in WPF filtern?
Wie kann ich datenvirtualisierte Elemente in WPF sortieren?

EDIT: Da, wenn Sie Ihre resultCollection auffüllen, feuert jedes Add() eine Sammlung geändert-Ereignis, das durch Ihre WPF-Steuerelemente behandelt wird. Das ist also keine gute Idee, da, wenn es 1000 Elemente zu Ihrer resultCollection hinzugefügt werden, dann gibt es 1000 Ereignisse durch Ihren Code (oder WPF-Steuerelemente) behandelt. Das ist unnötig. Sie können diese Ereignisse also unterdrücken, wie Marijn vorgeschlagen hat.

Aber ich würde Ihnen empfehlen, diesen Trick anzuwenden, um die unnötigen Ereignisse zu unterdrücken:

ObservableCollection<Result> temp = resultCollection;
resultCollection = null ; // make it null so it will not fire any event anymore to be handled by wpf!
temp.clear()

foreach(/*your code*/)
{
     temp.Add(item); //since temp is not bound to any control, temp's collection changed event will not be handled by anyone! Means, No Handler, No Code to Execute, No time waste!
}

resultCollection = temp ; //this fires event, which is handled by your code/ wpf.

1voto

Marijn Punkte 10067

Sie können die Methode OnCollectionChanged überschreiben, um CollectionChanged-Ereignisse vorübergehend zu unterdrücken, wenn Sie einer beobachtbaren Sammlung eine große Menge hinzufügen. Siehe zum Beispiel diese Stelle .

0voto

rossisdead Punkte 2073

Bitte entschuldigen Sie mich, wenn dies falsch ist, ich habe noch keine Erfahrung mit dem neuen async/Task-Zeug.

Kurze Antwort: Setzen Sie SerialResults erst dann auf Ihre neue ObservableCollection, wenn Sie die Hinzufügung von Objekten zu dieser abgeschlossen haben.

Lange Antwort: ObservableCollection muss sein CollectionChanged-Ereignis jedes Mal auslösen, wenn Sie ihr ein Element hinzufügen. Da die ObservableCollection an ein ItemsControl gebunden ist, muss das ItemsControl jedes Mal, wenn dieses Ereignis ausgelöst wird, das Fenster neu zeichnen, wodurch alles andere blockiert wird.

Siehe Marijns Beitrag über das Erstellen eines eigenen ObservableCollection-Typs für einen Workaround.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X