简体   繁体   English

如何解决WPF ListView SelectedItems性能不佳的问题?

[英]How can I get around this poor WPF ListView SelectedItems performance?

Here is my code (it searches a WPF ListView for all matches and then selects all of them): 这是我的代码(它在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;
            }

Here are the results with 10,000 ListView items: 以下是10,000个ListView项目的结果:

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

So, clearly my problem is with the loop: 所以,很明显我的问题是循环:

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

I feel like there must be a better way to add to the selected items list, or at least block data template updates (or something like that) on the list until all selected items have been set. 我觉得必须有一种更好的方法来添加到选定项列表中,或者至少阻止列表中的数据模板更新(或类似的操作),直到所有选定项都被设置为止。 Is there any way to get better performance when trying to select multiple items programmatically in a WPF ListView? 在WPF ListView中尝试以编程方式选择多个项目时,有什么方法可以获得更好的性能?

Instead of adding selected items one by one to the SelectedItems property, you may call the SetSelectedItems method. 您可以调用SetSelectedItems方法,而不是将选定的项目一个接一个地添加到SelectedItems属性。 Unfortunately the method is protected, so you have to create a derived ListBox that makes it publicly available: 不幸的是,该方法受到保护,因此您必须创建一个派生的ListBox使其公开可用:

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

Alright. 好的。 You already accepted an answer to this question, But I wanted to show a different approach anyways: 您已经接受了此问题的答案,但是无论如何,我想展示一种不同的方法:

在此处输入图片说明

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

Code Behind: 背后的代码:

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

ViewModel: ViewModel:

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

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

Data Filter: 资料筛选器:

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

    public string DisplayName { get; set; }
}

Random Data Source (just a bunch of boilerplate) 随机数据源(只是一堆样板)

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)],
        };
    }
}

This approach has the following advantages over a traditional code-behind approach: 与传统的代码隐藏方法相比,此方法具有以下优点:

  • It decouples the UI and the logic. 它使UI和逻辑脱钩。 You operate against your own defined classes instead of dealing with the (sometimes arcane and obscure) WPF object model. 您可以对自己定义的类进行操作,而不是处理(有时是晦涩难懂的)WPF对象模型。
  • Since your code does not actually depend on any specific UI element type, you may change the UI to a "3D rotating pink elephant" and it would still work. 由于您的代码实际上并不依赖于任何特定的UI元素类型,因此您可以将UI更改为“ 3D旋转粉红色大象”,并且仍然可以使用。 It enables much more customizability of the view without compromising any code or logic. 它在不损害任何代码或逻辑的情况下实现了视图的更多可定制性。
  • It is easily reusable (you can go as far as to create a SearchViewModel<T> and a DataFilter<T> and reuse these on many different entity types. 它很容易重用(您可以创建一个SearchViewModel<T>和一个DataFilter<T>并在许多不同的实体类型上重用它们。
  • It is unit-testable. 它可以进行单元测试。

There's a lot of information in comments, so I'm going to summarize: 评论中有很多信息,所以我将总结一下:

  1. The way you're supposed to update the selection in a multi-select ListView is by modifying the SelectedItems property. 在多选ListView中更新选择的方式是通过修改SelectedItems属性。 There is no way to get or set by index in WPF. 在WPF中无法通过索引获取或设置。 Everything operates by item, which means every selection operation requires finding an item in the list. 一切都按项目操作,这意味着每个选择操作都需要在列表中找到一个项目。 For a large list, this can be slow. 对于较大的列表,这可能会很慢。
  2. ListView defines a "bulk change" method, SetSelectedItems , that can be used to select multiple items with one call. ListView定义了一个“大量更改”方法SetSelectedItems ,可用于一次调用选择多个项目。 It's declared protected , so you either need to sub-class ListView, or call it with reflection. 它被声明为protected ,因此您需要子类化ListView,或者通过反射对其进行调用。 Under the hood it still has to find the items in the list to mark them as selected, so while it's faster, it can still be very slow for large lists. 在后台,它仍然必须在列表中找到项目以将其标记为已选中,因此虽然速度更快,但对于大型列表而言仍然很慢。
  3. An alternative approach is to move the IsSelected value into the item itself, and use data binding. 另一种方法是将IsSelected值移到项目本身,然后使用数据绑定。 This approach is very fast, but falls apart when UI virtualization is enabled. 这种方法非常快速,但是在启用UI虚拟化时会分崩离析。 Since very large lists will almost always want to use virtualization, this is a non-starter. 由于非常大的列表几乎总是希望使用虚拟化,因此这是一个入门。 I haven't yet found a way around the issues. 我还没有找到解决问题的方法。

Bottom line: there is no way to quickly select a large number of items in a WPF ListView. 底线:无法在WPF ListView中快速选择大量项目。

Anyone who wants to experiment can use the "selection test" in the DisasmUiTest project as a starting point. 任何想尝试的人都可以使用DisasmUiTest项目中的“选择测试”作为起点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM