为什么在.NET 4.6中更改WPF DataGrid的ItemsSource非常缓慢?

3
例如,尝试创建此简单的WPF窗口:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBox Grid.Row="0" Text="{Binding Path=Filter, UpdateSourceTrigger=PropertyChanged}" />

        <DataGrid Grid.Row="1" ItemsSource="{Binding FilteredList}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding}" />
            </DataGrid.Columns>
        </DataGrid>

    </Grid>
</Window>

使用以下代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;

public partial class MainWindow : Window, INotifyPropertyChanged
{
    List<string> items = new List<string>();
    string filter = string.Empty;

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        for (int i = 0; i < 100; i++) items.Add(i.ToString());
    }

    public IEnumerable<string> FilteredList
    {
        get { return this.items.Where(item => item.Contains(filter)).ToArray(); }
    }

    public string Filter
    {
        get { return filter; }
        set
        {
            if (filter != value)
            {
                filter = value;
                PropertyChanged(this, new PropertyChangedEventArgs("FilteredList"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged = (s, e) => { };
}

网格中只有100个字符串。文本框允许过滤字符串。

但是,例如将123写入过滤器,然后再删除它会导致应用程序多秒钟的冻结。为什么不是即时的?

编辑:在.NET 4.5中,甚至带有10,000个项目也是即时的。这似乎是.NET 4.6中的一个回归?


1
输入“123”,然后删除文本会为FilteredList引发四次PropertyChanged事件。每次属性更改时,整个DataGrid内容都会被重新创建,因为整个ItemsSource集合都被替换。还要注意,在属性getter中的ToArray()是多余的。 - Clemens
@Clemens 但是为什么这会成为需要多秒的问题呢? - Peter
2个回答

1
问题在于每次都替换整个集合。你应该使用CollectionViewSource作为DataGrid的源。CollectionViewSource接受过滤表达式。因此,您可以基于原始IEnumerable创建一个CollectionViewSource,并根据文本框值创建一个过滤方法。然后调用CollectionViewSource的Refresh方法。

但是这为什么如此重要呢?为什么绑定一个小集合需要多秒钟的时间? - Peter
在算法中,顺序很重要,在这种情况下是O(n),但常数也很重要。在您的情况下,每次输入一个字母时,都会计算100个字符串并进行过滤。然后将这些字符串打印到屏幕上(又多了100个操作)。例如,如果您的过滤词是123,则它有3个字母,变成了32100 = 600个操作。CollectionViewSource答案在内部计算原始源,如果设置了过滤器,则从屏幕中删除或添加元素。常数会降低。 - aperezfals
1
但是600个操作算不了什么。常数因子太高了。 看起来是.NET 4.6或其他版本的回归问题。在4.5中,即使列表中有1万个项目,它也会快得多(几乎是瞬间完成),这是预期的情况。 - Peter

1

DataGrid.EnableColumnVirtualization 默认为 False。将其设置为 True 有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接