Как улучшить предварительную производительность обновления связывания списка в WPF? - PullRequest
2 голосов
/ 09 декабря 2010

Итак, вот сценарий, у меня есть операция, которая очищает ObservableCollection, запускает запрос и повторно заполняет коллекцию результатами запроса.Эта коллекция привязана к списку.Вот пример: 500 результатов запроса приводят к серьезному времени обновления, которое блокирует пользовательский интерфейс из-за того, что воспринимается как «слишком длинный» (в действительности это составляет 0,5-2 секунды в большинстве систем). В любом случае, я знаю, что запрос достаточно быстрыйТак же, как и добавление элементов, я сузил его до уровня представления.

Проводя некоторое тестирование, я получаю заметно лучшую производительность, если удаляю шаблон элемента из списка (не очень), но не настолько лучше, чтобы он даже соответствовал ожиданиям, которые мне сообщили.Я обновил привязки так, чтобы они были применимыми, я изменил режим виртуализации на повторный цикл и убедился, что использую статические ресурсы, ни одно из вышеперечисленного не оказало заметного влияния на перерисовку.Мне было интересно, если у кого-нибудь есть хорошая идея, как улучшить производительность списка, заполненного большим количеством элементов?

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

И так как об этом спрашивали несколько раз ниже, нет, это не так.моя наблюдаемая коллекция ... Я подавляю событие, выполняя пакет обновлений и переназначая источник элементов на новый экземпляр.

    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;
    }

Ответы [ 3 ]

2 голосов
/ 10 декабря 2010

Если вы хотите отобразить результаты (вашего запроса), в которых содержится большое количество данных, то я бы предложил вам использовать DataPager с DataGrid / ListView / ListBox и показывать только столько элементов, сколько доступно пространствоможно разместить (без создания вертикальной полосы прокрутки).Для этого вам нужно написать элемент управления DataPager, так как он не поставляется с WPF (хотя в Silverlight есть DataPager!).(Я написал DataPager для своего проекта, чтобы решить аналогичную проблему. Интересно написать свои собственные элементы управления!)

Но даже до этого вы можете попробовать это: (это может работать)

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

Вместо этого:

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

Вы также можете взглянуть на эти статьи:

Виртуализация данных
Виртуализация пользовательского интерфейса
Как отфильтровать виртуализированные элементы данных в WPF?
Как отсортировать виртуализированные элементы данных в WPF?

РЕДАКТИРОВАТЬ: поскольку при заполнении resultCollection, каждый Add () запускает событие изменения коллекции, которое обрабатывается вашими элементами управления WPF.Так что это не очень хорошая идея, поскольку, если к вашей коллекции resultCollection нужно добавить 1000 элементов, то ваш код обрабатывает 1000 событий (или элементов управления WPF).В этом нет необходимости.Таким образом, вы можете подавить эти события, как предложила Марин.

Но я бы посоветовал вам сделать этот трюк для подавления ненужных событий:

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.
1 голос
/ 10 декабря 2010

Может потребоваться переопределить метод OnCollectionChanged для временного подавления событий CollectionChanged при массовом добавлении в наблюдаемую коллекцию.Смотрите, например, этот пост .

0 голосов
/ 10 декабря 2010

Пожалуйста, извините, если это не так, у меня пока нет опыта работы с новыми асинхронными / заданиями.

Краткий ответ: не устанавливайте SerialResults в новую коллекцию ObservableCollection, пока не завершите добавление к ней элементов.

Длинный ответ: ObservableCollection должна вызывать событие CollectionChanged каждый раз, когда вы добавляете в него элемент. Поскольку ObservableCollection привязан к ItemsControl, каждый раз, когда происходит событие, ItemsControl должен перерисовывать окно, блокируя что-либо еще.

См. Сообщение Марийн о создании собственного типа ObservableCollection для обходного пути.

...