繁体   English   中英

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

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

这是我的代码(它在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中尝试以编程方式选择多个项目时,有什么方法可以获得更好的性能?

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

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

好的。 您已经接受了此问题的答案,但是无论如何,我想展示一种不同的方法:

在此处输入图片说明

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

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

数据项:

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>并在许多不同的实体类型上重用它们。
  • 它可以进行单元测试。

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

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

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

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

暂无
暂无

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

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