如何在WPF中提高列表框的绑定更新性能?

时间:2010-12-09 20:21:51

标签: c# wpf performance data-binding

所以这是场景,我有一个操作可以清除ObservableCollection,运行查询,并使用查询结果重新填充集合。此集合通过列表框进行数据绑定。这是踢球者,500个查询结果会导致一些严重的更新时间,这会阻止用户界面被视为“太长”(实际上在大多数系统上都是.5-2秒)无论哪种方式,我都知道查询速度足够快,就像添加元素一样,所以我把它缩小到表示层。

进行一些测试,如果我从列表框中删除项目模板(duh),性能会明显提高,但不会太好,甚至不能满足传达给我的期望。我将绑定更新为适用的一种方式,我将虚拟化模式更改为回收,并确保我使用的是静态资源,以上都没有对重新绘制产生明显影响。我想知道是否有其他人知道如何提高列表框中填充大量项目的性能?

<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 个答案:

答案 0 :(得分:2)

如果你想显示(你的查询中)恰好是大量数据的结果,那么我建议你将DataPager与DataGrid / ListView / ListBox一起使用,并且只显示与可用空间一样多的项目可以容纳(不创建垂直滚动条)。为此,您需要编写一个DataPager控件,因为它没有WPF(尽管Silverlight有DataPager!)。 (我为我的项目编写了DataPager来解决类似的问题。编写自己的控件很有趣!)

但即使在此之前你也可以试试这个:( 可能工作)

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

而不是:

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

您可能还想查看这些文章:

Data virtualization
UI Virtualization
How can I filter data virtualized items in WPF?
How can I sort data virtualized items in WPF?

编辑:因为当你填充resultCollection时,每个Add()都会触发一个由WPF控件处理的集合更改事件。所以这不是一个好主意,因为如果有1000个项目要添加到您的resultCollection中,那么您的代码(或WPF控件)会处理1000个事件。这是不必要的。所以你可以像Marijn建议的那样压制这些事件。

但我建议你这样做来抑制不必要的事件:

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 :(得分:1)

您可能希望重写OnCollectionChanged方法,以便在对可观察集合进行批量添加时临时禁止CollectionChanged事件。例如,请参阅this post

答案 2 :(得分:0)

如果这是错误的,请原谅我,我对新的async / Task内容没有任何经验。

简短回答:在完成向其添加项目之前,不要将SerialResults设置为新的ObservableCollection。

答案很长:ObservableCollection每次向其添加项目时都必须提升其CollectionChanged事件。由于ObservableCollection绑定到ItemsControl,每次该事件触发时ItemsControl都需要重绘窗口,阻止其他任何事情发生。

请参阅Marijn关于为解决方法创建自己的ObservableCollection类型的帖子。