Real-time filtering of lists

I would like to be able to filter a list containing 1000 lines with a length of 50-4000 characters, as the user types into the text field without delay.

I am currently using a timer that updates the list after a TextChangedtext field event has not been fired in 300 ms. However, it is quite jerky, and ui sometimes freezes for a moment.

What is the normal way to implement such functions?

Edit: I am using winforms and .net2.

thank

Here is a stripped down version of the code I'm currently using:

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
source share
5 answers

You can do two things:

  • UI , . - 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
    });
    
  • . - StartWith - Contains?

    . , , - , . , 100 000 ListBox, 100 - ( .Take(100)). , . Rx, .

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

Azerax RX.

, :

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

. (*) .

+2

, @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

, , , LINQ, , .

What I did was define a List (Of) collection to keep the original list of information that I upload to the ListBox and Filtered List (Of) collection to hold the resulting filtered subset.

I used the RegEx namespace for filtering, but you could use the template system inherent in the String structure. Here is the code I used to do this work.

   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
source

All Articles