【问题标题】:How can I improve the binding update preformance of a listbox in WPF?如何提高 WPF 中列表框的绑定更新性能?
【发布时间】:2010-12-09 20:21:50
【问题描述】:

这里是这样的场景,我有一个清除 ObservableCollection、运行查询并使用查询结果重新填充集合的操作。此集合由列表框数据绑定。这是踢球者,500 个查询结果会导致一些严重的更新时间,这会阻止用户界面被认为“太长”(实际上在大多数系统上是 0.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;
    }

【问题讨论】:

    标签: c# wpf performance data-binding


    【解决方案1】:

    如果您想显示(查询的)结果恰好是大量数据,那么我建议您将 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.
    

    【讨论】:

    • 数据模板上的虚拟化堆栈面板,我之前也完全去掉了,没有明显区别。
    • 另外,这就是我的代码过去是如何指向上述可观察的集合代码,它是重新填充和渲染代码似乎很慢。
    • @Firoso.. 似乎是不必要的事件使您的代码变慢,请按照我上面的建议尝试抑制它们!
    • @Firoso.. 你能从你的演示者/视图模型中发布相关代码吗?
    • @Nawaz,我错过了什么 O_O 我刚刚添加了 VM 代码,实际上将其更改为列表而不是可观察的集合,没有明显的变化,因为我正在构建一个新列表并重新分配它。
    【解决方案2】:

    在批量添加到可观察集合时,您可能希望重写 OnCollectionChanged 方法以暂时抑制 CollectionChanged 事件。参见例如this post

    【讨论】:

    • 阅读您的问题的更新后,我认为这对您没有帮助。
    【解决方案3】:

    如果有错误请见谅,我对新的 async/Task 东西还没有任何经验。

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

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

    请参阅 Marijn 的帖子,了解如何创建自己的 ObservableCollection 类型以了解解决方法。

    【讨论】:

    • 如上贴,我不做这个O_O
    猜你喜欢
    • 2011-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-06
    • 2011-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多