如何解决这个糟糕的WPF ListView SelectedItems性能?

时间:2014-02-21 17:15:47

标签: c# .net wpf listview

这是我的代码(它在WPF ListView中搜索所有匹配项,然后选择所有匹配项):

            public bool FindAll(LogFilter filter, bool matchCase)
            {
                lastLogFilter = filter;
                lastMatchCase = matchCase;
                MatchSearcher quickSearchSearcher = new MatchSearcher(filter, !matchCase);
                bool foundOnce = false;
                Stopwatch watch = new Stopwatch();
                watch.Start();
                var query = from x in listView.Items.Cast<LogRecord>() where quickSearchSearcher.IsMatch(x, false) select x;
                watch.Stop();
                Console.WriteLine("Elapsed milliseconds to search: {0}.", watch.ElapsedMilliseconds);
                if (query.Count() > 0)
                {
                    foundOnce = true;
                    listView.SelectedItems.Clear();
                    watch.Restart();
                    foreach (LogRecord record in query)
                    {
                        listView.SelectedItems.Add(record);
                    }
                    watch.Stop();
                    Console.WriteLine("Elapsed milliseconds to select: {0}.", watch.ElapsedMilliseconds);
                    listView.ScrollIntoView(query.First());
                }
                return foundOnce;
            }

以下是10,000个ListView项目的结果:

Elapsed milliseconds to search: 0.
Elapsed milliseconds to select: 36385.

所以,显然我的问题在于循环:

foreach (LogRecord record in query)
{
    listView.SelectedItems.Add(record);
}

我觉得必须有更好的方法来添加到所选项目列表,或至少阻止列表上的数据模板更新(或类似的东西),直到所有选定项目都已设置。尝试在WPF ListView中以编程方式选择多个项目时,有没有办法获得更好的性能?

3 个答案:

答案 0 :(得分:5)

您可以调用SetSelectedItems方法,而不是逐个将所选项添加到SelectedItems属性中。不幸的是,该方法受到保护,因此您必须创建一个派生的ListBox,使其公开可用:

public class MyListView : ListView
{
    public void SelectItems(IEnumerable items)
    {
        SetSelectedItems(items);
    }
}

答案 1 :(得分:3)

好的。你已经接受了这个问题的答案,但我还想表现出不同的方法:

enter image description here

XAML:

<Window x:Class="WpfApplication1.ListViewSearch"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListViewSearch" Height="300" Width="300">
    <DockPanel>
        <DockPanel DockPanel.Dock="Left" Margin="2">
            <Button DockPanel.Dock="Bottom" Content="Find All" Margin="2" Click="FindAll_Click"/>

            <ListBox ItemsSource="{Binding Filters}"
                     SelectedItem="{Binding SelectedFilter}"
                     DisplayMemberPath="DisplayName"/>
        </DockPanel>

        <ListView ItemsSource="{Binding Items}">
            <ListView.View>
                <GridView>
                    <GridViewColumn DisplayMemberBinding="{Binding FirstName}" Header="First Name"/>
                    <GridViewColumn DisplayMemberBinding="{Binding LastName}" Header="Last Name"/>
                </GridView>
            </ListView.View>

            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                </Style>
            </ListView.ItemContainerStyle>
        </ListView>

    </DockPanel>
</Window>

代码背后:

public partial class ListViewSearch : Window
{
    private ViewModel ViewModel;

    public ListViewSearch()
    {
        InitializeComponent();

        DataContext = ViewModel = new ViewModel();
    }

    private void FindAll_Click(object sender, RoutedEventArgs e)
    {
        ViewModel.Filter();
    }
}

视图模型:

public class ViewModel
{
    public ViewModel()
    {
        Items = new ObservableCollection<DataItem>(RandomDataSource.GetRandomData());
        Filters = new ObservableCollection<DataFilter>();

        Filters.Add(new DataFilter()
        {
            DisplayName = "First Name starting with A",
            FilterExpression = x => x.FirstName.ToLower().StartsWith("a")
        });

        Filters.Add(new DataFilter()
        {
            DisplayName = "Last Name starting with E",
            FilterExpression = x => x.LastName.ToLower().StartsWith("e")
        });
    }

    public ObservableCollection<DataItem> Items { get; private set; }

    public DataFilter SelectedFilter { get; set; }

    public ObservableCollection<DataFilter> Filters { get; private set; }

    public void Filter()
    {
        if (SelectedFilter == null)
            return;

        foreach (var item in Items)
            item.IsSelected = SelectedFilter.FilterExpression(item);
    }
}

数据项:

public class DataItem : INotifyPropertyChanged
{
    private bool _isSelected;

    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            _isSelected = value;
            OnPropertyChanged("IsSelected");
        }
    }

    public string LastName { get; set; }

    public string FirstName { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

数据过滤器:

public class DataFilter
{
    public Func<DataItem, bool> FilterExpression { get; set; }

    public string DisplayName { get; set; }
}

随机数据源(只是一堆样板)

public static class RandomDataSource
{
    private static string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
    private static List<string> words;
    private static int maxword;
    private static Random random;

    public static List<DataItem> GetRandomData()
    {
        random = new Random();
        words = TestData.Split(' ').ToList();
        maxword = words.Count - 1;

        return Enumerable.Range(0, 10000)
                         .Select(x => GetRandomItem())
                         .ToList();
    }

    private static DataItem GetRandomItem()
    {
        return new DataItem()
        {
            LastName = words[random.Next(0, maxword)],
            FirstName = words[random.Next(0, maxword)],
        };
    }
}

与传统的代码隐藏方法相比,此方法具有以下优势:

  • 它解耦了UI和逻辑。您针对自己定义的类进行操作,而不是处理(有时是神秘且模糊的)WPF对象模型。
  • 由于您的代码实际上并不依赖于任何特定的UI元素类型,因此您可以将UI更改为“3D旋转粉红色大象”,它仍然有效。它可以在不影响任何代码或逻辑的情况下实现视图的更多自定义。
  • 它很容易重复使用(您可以创建SearchViewModel<T>DataFilter<T>并在许多不同的实体类型上重复使用它们。
  • 可以单元测试。

答案 2 :(得分:0)

评论中有很多信息,所以我要总结一下:

  1. 您应该在多选ListView中更新选择内容的方法是修改SelectedItems属性。在WPF中无法通过索引获取或设置。一切都按项目操作,这意味着每个选择操作都需要在列表中找到一个项目。对于较大的列表,这可能会很慢。
  2. ListView定义了一种“批量更改”方法SetSelectedItems,可用于一次调用选择多个项目。它被声明为protected,因此您需要子类化ListView,或者使用反射对其进行调用。在后台,它仍然必须在列表中找到项目以将其标记为已选中,因此虽然速度更快,但对于大型列表而言仍然很慢。
  3. 另一种方法是将IsSelected值移至项目本身,并使用数据绑定。这种方法非常快速,但是在启用UI虚拟化时会分崩离析。由于非常大的列表几乎总是希望使用虚拟化,因此这是一个入门。我还没有找到解决问题的方法。

底线:无法在WPF ListView中快速选择大量项目。

任何想尝试的人都可以使用DisasmUiTest项目中的“选择测试”作为起点。