Фильтрация больших коллекций данных в ListView C # - PullRequest
0 голосов
/ 17 октября 2018

Я пытаюсь использовать поле фильтра, которое фильтрует большой список данных, сохраненных в ObservableCollection, в зависимости от того, содержит ли элемент строку, и отображает результаты в ListView.

В настоящее время я использую конвертер для достижения этой цели.Он работает, проверяя, содержит ли целевая строка строку фильтра, используя простой метод сравнения без учета регистра.

private static bool Contains(string source, string toCheck, StringComparison comp = StringComparison.OrdinalIgnoreCase)
{
    return source?.IndexOf(toCheck, comp) >= 0;
}

Этот подход, кажется, хорошо работает для меньшего числа записей (несколько сотен).Но размер данных, с которыми я работаю, может варьироваться от 50 тыс. До 200 тыс. Записей.

Существует ли способ эффективной фильтрации списка без значительных падений производительности при поиске в коллекциях данных примерно 200000 записей.

MCVE ниже.

XAML

<Window x:Class="FastFilter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:FastFilter"
        mc:Ignorable="d"
        Title="Fast Filter" Height="450" Width="800">

    <Window.Resources>
        <local:FilterConverter x:Key="FilterConverter"/>
    </Window.Resources>

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

        <TextBox Text="{Binding Path=FilterString, UpdateSourceTrigger=PropertyChanged}"/>
        <ListView Grid.Row="1"
                  ItemsSource="{Binding Path=Infos}">
            <ListView.ItemContainerStyle>
                <Style TargetType="{x:Type ListViewItem}">
                    <Setter Property="Visibility">
                        <Setter.Value>
                            <MultiBinding Converter="{StaticResource FilterConverter}">
                                <Binding Path="DataContext.FilterString" RelativeSource="{RelativeSource AncestorType=ListView}"/>
                                <Binding Path="Text"/>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <!-- List Box Item Layout -->
                        <StackPanel Orientation="Horizontal">
                            <Label Content="Text:"/>
                            <Label Content="{Binding Text}"/>
                        </StackPanel>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

CS

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;

namespace FastFilter
{
    public partial class MainWindow : INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            for (int i = 0; i < 200000; i++)
            {
                Infos.Add(new ObjectInfo(Guid.NewGuid().ToString()));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private string filterString = string.Empty;
        public string FilterString
        {
            get => filterString;
            set
            {
                filterString = value; 
                OnPropertyChanged();
            }
        }

        private ObservableCollection<ObjectInfo> infos = new ObservableCollection<ObjectInfo>();
        public ObservableCollection<ObjectInfo> Infos {
            get => infos;
            set {
                infos = value;
                OnPropertyChanged();
            }
        }
    }

    public class ObjectInfo
    {
        public ObjectInfo(string text)
        {
            Text = text;
        }

        public string Text { get; }
    }

    public class FilterConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            string filter = (string)values[0];
            string checkStringContains = (string)values[1];

            return !(string.IsNullOrWhiteSpace(checkStringContains) || string.IsNullOrWhiteSpace(filter))
                ? Contains(checkStringContains, filter) ? Visibility.Visible : Visibility.Collapsed
                : Visibility.Visible;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }

        private static bool Contains(string source, string toCheck, StringComparison comp = StringComparison.OrdinalIgnoreCase)
        {
            return source?.IndexOf(toCheck, comp) >= 0;
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 17 октября 2018

Попробуйте использовать ICollectionView.

xaml

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

    <TextBox TextChanged="FilterTextChanged" Text="{Binding Path=FilterString, UpdateSourceTrigger=PropertyChanged}"/>
    <ListView 
              x:Name="InfosListView"
              Grid.Row="1"
              ItemsSource="{Binding Path=Infos}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <!-- List Box Item Layout -->
                    <StackPanel Orientation="Horizontal">
                        <Label Content="Text:"/>
                        <Label Content="{Binding Text}"/>
                    </StackPanel>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

CS

    private void FilterTextChanged(object sender, TextChangedEventArgs e)
    {
        UpdateFilter();
    }

    private void UpdateFilter()
    {
        //NOTE: bellow comment only applies to DataGrids.
        //Calling commit or cancel edit twice resolves exceptions when trying to filter the DataGrid.
        //https://stackoverflow.com/questions/20204592/wpf-datagrid-refresh-is-not-allowed-during-an-addnew-or-edititem-transaction-m
        //CommitEdit();
        //CommitEdit();

        ICollectionView view = CollectionViewSource.GetDefaultView(Infos);
        if (view != null)
        {
            view.Filter = delegate (object item)
            {
                if (item is ObjectInfo objectInfo)
                {
                    return objectInfo.Text.Contains(FilterString);
                }
                return false;
            };
        }
    }

Следующим обновлением будет добавление DispatcherTimer к событию textaged, чтобы только фильтробновляется после того, как текст не вводится в течение примерно секунды вместо каждого символа

0 голосов
/ 17 октября 2018

Для такого рода запросов adhoc вам нужно просмотреть всю коллекцию, чтобы создать набор отфильтрованных элементов, поэтому вы не можете ничего с этим поделать.

Мое предложение помочь с эффективностью неповторное выполнение фильтра сразу после каждого (одиночного символа) изменения в строку фильтра.Вместо этого пусть каждое изменение в FilterString (повторно) запускает объект таймера с периодом, скажем, 1 секунда, и фактически выполняет фильтрацию только после срабатывания таймера.Или вы можете использовать какую-то буферизованную конструкцию реактивных расширений для достижения того же результата.

...