Фильтрация списка в реальном времени - PullRequest
5 голосов
/ 05 августа 2010

Я хотел бы иметь возможность фильтровать список, содержащий 1000 строк, каждая длиной 50–4000 символов, поскольку пользователь вводит текст в поле без задержки.

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

Каков нормальный способ реализации функциональности, подобной этой?

Редактировать: я использую winforms и .net2.

Спасибо

Вот урезанная версия кода, который я сейчас использую:

string separatedSearchString = this.filterTextBox.Text;

List<string> searchStrings = new List<string>(separatedSearchString.Split(new char[] { ';' }, 
                                              StringSplitOptions.RemoveEmptyEntries));

//this is a member variable which is cleared when new data is loaded into the listbox
if (this.unfilteredItems.Count == 0)
{
    foreach (IMessage line in this.logMessagesListBox.Items)
    {
        this.unfilteredItems.Add(line);
    }
}

StringComparison comp = this.IsCaseInsensitive
                        ? StringComparison.OrdinalIgnoreCase
                        : StringComparison.Ordinal;

List<IMessage> resultingFilteredItems = new List<IMessage>();

foreach (IMessage line in this.unfilteredItems)
{
    string message = line.ToString();
    if(searchStrings.TrueForAll(delegate(string item) { return message.IndexOf(item, comp) >= 0; }))
    {
        resultingFilteredItems.Add(line);
    }
}

this.logMessagesListBox.BeginUpdate();
this.logMessagesListBox.Items.Clear();
this.logMessagesListBox.Items.AddRange(resultingFilteredItems.ToArray());
this.logMessagesListBox.EndUpdate();

Ответы [ 5 ]

2 голосов
/ 28 декабря 2011

Ответ Azerax является правильным с новой версией RX.

Если вы хотите отделить код от элементов пользовательского интерфейса, вы можете получить:

input.ObserveOn(SynchronizationContext.Current).Subscribe(filterHandler, errorMsg); 

уведомление обратно в поток пользовательского интерфейса.В противном случае дроссельная заслонка (*) не будет действовать.

1 голос
/ 29 июня 2011

Прежде всего, спасибо @lasseespeholt, за то, что я начал эту идею, очень новую для меня. Но на самом деле Rx очень интересно сделать, делает жизнь намного проще:)

Мне пришлось реализовать аналогичную вещь с древовидным представлением, содержащим узлы (только родительского уровня), отфильтрованные по событию изменения текста в WinForms.

Приложение продолжало падать на меня по какой-то странной причине.

Я нашел файл PDF на сайте MSDN @ MSDN Rx ( Ссылка для скачивания PDF - см. Стр. 25), в котором решалась аналогичная проблема и описана проблема перекрестного доступа.

Вот исправление, которое мне помогло, решение также состоит в том, чтобы использовать ObserveOn перед подпиской.

Вот пример кода, который использует более позднюю версию Rx - 1.0.10605.1

    /// <summary>
    /// Attach an event handler for the text changed event
    /// </summary>
    private void attachTextChangedEventHandler()
    {            
    var input = (from evt in Observable.FromEventPattern<EventArgs>(textBox1,"TextChanged")
    .select ((TextBox)evt.Sender).Text)
    .DistinctUntilChanged()
    .Throttle(TimeSpan.FromSeconds(1));
    input.ObserveOn(treeView1).Subscribe(filterHandler, errorMsg);
    }
    private void filterHandler(string filterText)
    {
        Loadtreeview(filterText);
    }
1 голос
/ 05 августа 2010

Вы можете сделать две вещи:

  1. Сделайте ваш интерфейс более отзывчивым со вторым потоком, который заботится о фильтрации. Действительно замечательная новая технология - Reactive Extensions (Rx), которая будет делать именно то, что вам нужно.

    Я могу привести пример. Я полагаю, вы используете WinForms? Часть вашего кода поможет.

    http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

    Вот небольшой тизер:

    Observable.Context = SynchronizationContext.Current;
    var textchanged = Observable.FromEvent<EventArgs>(textBox1, "TextChanged");
    
    textchanged.Throttle(300).Subscribe(ea =>
    {
        //Here 300 milisec. is gone without TextChanged fired. Do the filtering
    });
    
  2. Сделайте ваш алгоритм фильтрации более эффективным. Вы фильтруете что-то вроде StartWith или что-то вроде Contains?

    Вы можете использовать что-то вроде дерева суффиксов или всех префиксов элементов списка и выполнять поиск. Но опишите, что вам нужно, и я найду что-то простое, но достаточно эффективное. Пользовательский интерфейс довольно тяжелый, если вы хотите отобразить 100 000 элементов в ListBox, но если вы берете только - скажем, 100 - он быстрый (раскомментируйте строку .Take (100)). Это также может быть сделано немного лучше, если поиск выполняется в другом потоке. Это должно быть легко с Rx, но я не пробовал.

Update

Попробуйте что-нибудь подобное. Он отлично работает здесь с 100.000 элементов длиной ~ 10 символов. Он использует Reactive Extensions (ссылка ранее).

Кроме того, алгоритм наивен и его можно сделать намного быстрее, если хотите.

private void Form1_Load(object sender, EventArgs e)
{
    Observable.Context = SynchronizationContext.Current;
    var textchanged = Observable.FromEvent<EventArgs>(textBox1, "TextChanged");

    //You can change 300 to something lower to make it more responsive
    textchanged.Throttle(300).Subscribe(filter);
}

private void filter(IEvent<EventArgs> e)
{
    var searchStrings = textBox1.Text.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

    //my randStrings is your unfiltered messages

    StringComparison comp = StringComparison.CurrentCulture; //Do what you want here

    var resultList = from line in randStrings
                     where searchStrings.All(item => line.IndexOf(item, comp) >= 0)
                     select line;

    //A lot faster but only gives you first 100 finds then uncomment:
    //resultList = resultList.Take(100);

    listBox1.BeginUpdate();
    listBox1.Items.Clear();
    listBox1.Items.AddRange(resultList.ToArray());
    listBox1.EndUpdate();
}
0 голосов
/ 10 февраля 2012

Не для этого, но все предложили разработки в стиле LINQ или дополнительные ресурсы для добавления в библиотеку служебных данных приложения.

Что я сделал, так это определил коллекцию List (Of) для хранения оригиналасписок информации, которую я в итоге загружаю в ListBox и коллекцию Filtered List (Of) для хранения результирующего отфильтрованного подмножества.

Я использовал пространство имен RegEx для фильтрации, но вы могли использовать систему шаблонов, присущуюс рамкой String.Вот код, который я использовал, чтобы выполнить работу.

   Private Sub txtNetRegex_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtNetRegex.TextChanged
      If String.IsNullOrEmpty(txtNetRegex.Text) Then
         btnNetALLToDB.Enabled = False
      Else
         btnNetALLToDB.Enabled = True

         Dim reg As New Regex(txtNetRegex.Text, RegexOptions.IgnoreCase)

         Me._netFilteredNames = New List(Of String)

         For Each s As String In Me._netNames
            On Error Resume Next
            If (reg.IsMatch(s)) Then
               Me._netFilteredNames.Add(s)
            End If
         Next

         LoadNetBox()
      End If
   End Sub
   Private Sub LoadNetBox()
      lbxNetwork.Items.Clear()
      lbxNetwork.Refresh()

      Dim lst As List(Of String)
      If Me.chkEnableNetFilter.Checked And (Me._netFilteredNames IsNot Nothing) Then
         lst = Me._netFilteredNames
      Else
         lst = Me._netNames
      End If

      If lst IsNot Nothing Then
         For Each s As String In lst
            lbxNetwork.Items.Add(s)
         Next
      End If

      lbxNetwork.Refresh()
   End Sub
0 голосов
/ 05 августа 2010

Вы можете изучить использование BindingSource для своего списка. Он может делать то, что вы ищете.

http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.filter.aspx

Пример использования DataTable, но тот же материал может применяться к списку.

http://msdn.microsoft.com/en-us/library/ya3sah92.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...