powered by simpleCommunicator - 2.0.28     © 2024 Programmizd 02
Map
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Быстрая фильтрация большого массива данных listview
7 сообщений из 7, страница 1 из 1
Быстрая фильтрация большого массива данных listview
    #40045777
glorsh66
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Есть возможность использовать Telerik.
В общем нужно сделать listview с возможностью фильтрации по введенным в textbox тексту.
Особенность что большое число записей - 200к или может даже больше.
Они причем выгружаются сразу.

Какой лучше всего выбрать элемент? Какой наиболее эффективный и быстрый?
Какой поддерживает виртуализацию - и самое осоновное - как реализовать фильтрацию по тексту.

В большинстве примеров - меняются всю коллекцию и обновляют элемент. Что естевственно совсем не производительно.
...
Рейтинг: 0 / 0
Быстрая фильтрация большого массива данных listview
    #40046018
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
glorsh66, а стандартный для WPF ICollectionView (CollectionViewSource) не справляется?
...
Рейтинг: 0 / 0
Быстрая фильтрация большого массива данных listview
    #40046044
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
glorsh66,

Вообще я потестил на обычном ListCollectionView и с 200000 записей, при небольшом объем данных по которым идет фильтрация, скорость обновления была приемлемой. Но если данные по которым осуществляется фильтрация (поиск вхождений) достаточно большие, а условие сложное и не особо быстрое, имеет смысл распараллелить процесс фильтрации (поиска вхождений) на множество ядер, судя по реализации ICollectionView этого не происходит.
В результате прирост на таких данных у меня был в ~4 раза. Это не бог весть какой показатель, но подозреваю, что с ростом времени выполнения предиката, эта разница будет увеличиваться, относительно однопоточного варианта.
На слабых процессорах прирост будет не таким хорошим, у меня i7.
Вот реализация, это набросок который требует обдуманной доработки, писал на коленке:
Это трудно назвать прокси классом и еще труднее представлением, но я так его назвал, смысл вроде понятен.
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
//Представление коллекции, в котором осуществляется фильтрация нашей коллекции.
public class FilterProxyView<T> : IList, INotifyCollectionChanged
{
    //Коллекция содержит результирующий список после фильтрации
    private List<T> _shadowList;
    //Приводит _shadowList к IList
    private IList GetShadowList() => (IList)_shadowList;
    //Коллекция источник
    private IEnumerable<T> _source;
    public Func<T, bool> Filter { get; }

    /// <summary> Создаем экземпляр представления коллекции для фильтрации </summary>
    /// <param name="source"> Исходная коллекция </param>
    /// <param name="filter"> Предикат фильтра </param>
    public FilterProxyView(IEnumerable<T> source, Func<T, bool> filter)
    {
        _source = source;
        Filter = filter;
        //Если источник INotifyCollectionChanged, то начинаем прослушивать
        if (source is INotifyCollectionChanged sourceChanged)
        {
            sourceChanged.CollectionChanged += OnSourceChanged;
        }
        UpdateShadow();

    }

    //Данный метод обновляет внутренний список (фильтрованный)
    private void UpdateShadow()
    {
        //Обновление выполняем параллельно
        _shadowList = _source
            .AsParallel()
            .Where(Filter)
            .ToList();

    }

    // Обработчик изменения исходной коллекции
    private void OnSourceChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        //Если исходная коллекция обновилась, обновляемся сами и обновляем слушателей
        Refresh();
    }


    public void Refresh()
    {
        //Обновляем список и слушателей
        UpdateShadow();
        OnCollectionChanged();
    }

    #region INotifyCollectionChanged
    //Реализация INotifyCollectionChanged
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    private readonly NotifyCollectionChangedEventArgs _resetArg 
        = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        
    //Сообщает слушателям, что нужно обновиться
    protected virtual void OnCollectionChanged()
    {
        CollectionChanged?.Invoke(this, _resetArg);
    }
    #endregion

    #region IList Implimentation
    //Реализация IList 
    public bool IsFixedSize => true;
    public bool IsReadOnly => GetShadowList().IsReadOnly;
    public int Count => _shadowList.Count;
    public bool IsSynchronized => GetShadowList().IsSynchronized;
    public object SyncRoot => GetShadowList().SyncRoot;
    public object this[int index] { get => _shadowList[index]; set => _shadowList[index] = (T)value; }
    bool IList.Contains(object value) => GetShadowList().Contains(value);
    int IList.IndexOf(object value) => GetShadowList().IndexOf(value);
    void ICollection.CopyTo(Array array, int index) => ((ICollection)_shadowList).CopyTo(array, index);
    IEnumerator IEnumerable.GetEnumerator() => _shadowList.GetEnumerator();
    int IList.Add(object value) => throw new NotSupportedException();
    void IList.Clear() => throw new NotSupportedException();
    void IList.Insert(int index, object value) => throw new NotSupportedException();
    void IList.Remove(object value) => throw new NotSupportedException();
    void IList.RemoveAt(int index) => throw new NotSupportedException();
    #endregion
}

//Генератор данных
class FakeDataGenerator
{

    private readonly Random _rnd = new Random(0);
    private string GenerateLongString()
    {
        var len = _rnd.Next(100, 500);
        var chars = new char[len];
        for (var i = 0; i < len; i++)
        {
            chars[i] = Convert.ToChar(_rnd.Next(Convert.ToInt32('A'), Convert.ToInt32('Z')));
        }
        return new string(chars);
    }

    public IEnumerable<Item> Generate(int count)
    {
        var items = new List<Item>();
        for (var i = 0; i < count; i++)
        {
            var item = new Item()
            {
                String1 = $"String 1 = {i}" + GenerateLongString(),
                String2 = $"String 2 = {i}" + GenerateLongString()
            };
            items.Add(item);
        }
        return items;
    }
}

//Главная модель окна
class MainModel
{
    private FilterProxyView<Item> _proxy;
    public MainModel()
    {
        //Генерируем данные (400 000 строк)
        var generator = new FakeDataGenerator();
        var items = generator.Generate(400_000);
        //Создаем прокси представление с 
        _proxy = new FilterProxyView<Item>(items, OnFilter);
        //Создаем представление коллекции, в ней фильтр не настраиваем!
        ItemsView = new ListCollectionView(_proxy);
    }
    private void UpdateFilter()
    {
        _proxy.Refresh();
    }

    // Предикат фильтрации 
    private bool OnFilter(Item item)
    {
        if (string.IsNullOrEmpty(_filterString)) return true;
        return item.String1.IndexOf(_filterString, StringComparison.OrdinalIgnoreCase) != -1
            || item.String2.IndexOf(_filterString, StringComparison.OrdinalIgnoreCase) != -1;
    }

    // Строка по которой идет фильтрация
    private string _filterString;
    public string FilterString 
    {
        set
        {
            if (string.Equals(_filterString, value, StringComparison.OrdinalIgnoreCase)) 
                return;
            _filterString = value;
            UpdateFilter();
        }
        get => _filterString;
    }

    // Представление коллекции для связывания с представлением
    public ICollectionView ItemsView { get; }
}

//Элемент списка
public class Item
{
    public string String1 { set; get; }
    public string String2 { set; get; }
}



Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
<Window x:Class="WpfApp17.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:WpfApp17"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainModel/>
    </Window.DataContext>
    <Grid>
        <DockPanel LastChildFill="True">
            <TextBox Margin="5" Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"
                     DockPanel.Dock="Top"/>
            <ListView ItemsSource="{Binding ItemsView}" Margin="5,0,5,5"
                      VirtualizingPanel.IsVirtualizing="True" 
                      VirtualizingPanel.VirtualizationMode="Standard">
                <ListView.View>
                    <GridView>
                        <GridView.Columns>
                            <GridViewColumn Width="120" Header="String1" DisplayMemberBinding="{Binding String1}"/>
                            <GridViewColumn Width="120" Header="String2" DisplayMemberBinding="{Binding String2}"/>
                        </GridView.Columns>
                    </GridView>
                </ListView.View>
            </ListView>
        </DockPanel>
    </Grid>
</Window>
...
Рейтинг: 0 / 0
Быстрая фильтрация большого массива данных listview
    #40046048
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
еще я в таких случаях делал Behavior, который обновлял содержимое поля FilterString, связанного с полем зависимости TextBox.TextProperty не по изменению, а через 300-400 мс после его последнего изменения, тогда если пользователь не куколд полный, можно за 1 запрос набрать больше 1 символа, ну или по кнопке Enter обновлять (дефолтное поведение). О том, что нужно нажать enter можно написать внутри текст бокса или еще как угодно.
Такое количество записей за 1 раз пользователю нафиг не нужно. Их невозможно обозреть, так зачем они загружаются? возможно достаточно первой сотни. К примеру, список ошибок, если даже он будет больше 100, смысла больше показывать нет. Можно даже первой десяткой или парой ограничиться.
Возможно нужен полнотекстовый поиск, а не то, что вы хотите. Если у вас сейчас такие объемы, что будет через пару дней\лет
...
Рейтинг: 0 / 0
Быстрая фильтрация большого массива данных listview
    #40046723
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Roman Mejtes , в ListView же есть виртуализация.
И ICollectionView будет фильтровать только отображаемые элементы, а не весь список.
Или я ошибаюсь?

Понимаю, что, допустим, для сортировки нужна обработка всего списка.
Но фильтрация работает последовательно, и, вроде, не должно быть тормозов для большого списка.
...
Рейтинг: 0 / 0
Быстрая фильтрация большого массива данных listview
    #40046791
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld Hasp
Понимаю, что, допустим, для сортировки нужна обработка всего списка.

Именно всего вне зависимости от виртуализации и количества отображаемых элементов.

Eld Hasp
Но фильтрация работает последовательно, и, вроде, не должно быть тормозов для большого списка.

Только она работает в GUI-потоке, и при более-менее сложной структуре визуального дерева элемента списка+более-менее сложной логике фильтрации (бывает, что достаточно просто фильтрации по регэкспу) тормоза становятся ощутимыми (не в нижеприведенном примере, но, тем не менее).
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
class TestModel : INotifyPropertyChanged
{
  readonly ObservableCollection<int> _items = new ObservableCollection<int>();
  readonly CollectionViewSource _itemsSource = new CollectionViewSource();

  public TestModel()
  {
    for(var n=0; n < 10000; n++)
      _items.Add(n);

    _itemsSource.Source = _items;
    Items = _itemsSource.View;
    Items.Filter = OnFilter;
    Prepared = 0;
  }

  bool OnFilter(object item)
  {
    Prepared++;
    return item is int n && n % 2 == 0;
  }

  public ICollectionView Items { get; }

  public int Prepared { get; private set; }

  bool _isFiltered;
  public bool IsFiltered
  {
    get => _isFiltered;
    set
    {
      _isFiltered = value;
      OnPropertyChanged();
      Items.Refresh();
      OnPropertyChanged(nameof(Prepared));
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

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


Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition />
  </Grid.RowDefinitions>
  <CheckBox
    Content="filter"
    IsChecked="{Binding IsFiltered, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
  <TextBlock
    Grid.Row="1"
    Text="{Binding Prepared, StringFormat='Prepared: {0}'}"
    Margin="0,5,0,0" />
  <ListView
    Grid.Row="2"
    Margin="0,5,0,0"
    ItemsSource="{Binding Items}" />
</Grid>
...
Рейтинг: 0 / 0
Быстрая фильтрация большого массива данных listview
    #40046826
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Суть моего примера как подключить параллелизм в фильтрацию, очевидно. что на данных из примера, простой способ (стандартный) будет тормозить, поиск заниматься 1,5 секунды на 2-3 символе, это уже явно ощущается. С AsParallel 100-200 мс.
С экземпляром Regex пробовал, скорость не сильно выше, чем с IndexOf. Удобно еще и тем, что при таком использовании фильтрации, все происходит прозрачно для конечного списка.
Как вариант, в том же представлении коллекции где осуществляется фильтрация, можно сделать порционную загрузку
Самый большой минус, при изменении исходной коллекции, это будет порождать сброс коллекции и её перестройка. Но это решаемо.
Но с таким объемом и количеством я не сталкивался, по этому в реальной жизни такого не применял, так как при 100 000к записей с разумным количеством информации всё летает без всяких выкрутасов.
...
Рейтинг: 0 / 0
7 сообщений из 7, страница 1 из 1
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Быстрая фильтрация большого массива данных listview
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]