列表框的实时过滤

时间:2010-08-04 20:16:24

标签: c# winforms listbox filter

我希望能够过滤包含1000个字符串的列表框,每个字符串长度为50到4000个字符,因为用户可以毫不拖延地在文本框中键入。

我目前正在使用一个计时器,在300毫秒内未触发文本框的TextChanged事件后更新列表框。然而,这是非常生涩,ui有时会暂时冻结。

实现与此类似的功能的常规方法是什么?

编辑:我正在使用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 个答案:

答案 0 :(得分:2)

Azerax的回答是新版RX的正确答案。

当您想要从UI元素中分离代码时,您可以:

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

这会将通知带回UI线程。否则,油门(*)将不起作用。

答案 1 :(得分:1)

你可以做两件事:

  1. 使用第二个负责过滤的线程,让您的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
    });
    
  2. 使您的过滤算法更有效率。你是用StartWith或像Contains这样的东西过滤的吗?

    您可以使用类似后缀树或列表项的所有前缀之类的内容并进行查找。但要准确描述你需要的东西,我会发现一些简单的东西 - 但足够有效。如果你想在ListBox中显示100.000项,但是如果你只需要 - 比如说100 - 那么UI很快(取消注释.Take(100)行)。如果在另一个线程中完成搜索,也可以做得更好。使用Rx应该很容易,但我还没有尝试过。

  3. <强>更新

    尝试这样的事情。它在这里工作得很好,有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();
    }
    

答案 2 :(得分:1)

首先,感谢@lasseespeholt,让我开始这个想法,对我来说很新。但确实Rx非常有趣,让生活更轻松:)

我必须使用包含节点(仅父级)的树视图实现类似的操作,这些节点由WinForms中的文本更改事件进行过滤。

由于一些奇怪的原因,该应用程序一直在崩溃。

我在MSDN网站上发现了一个PDF MSDN RxPDF download link - 请参阅第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);
    }

答案 3 :(得分:0)

您可以考虑为List使用BindingSource。它可以做你想要的。

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

使用DataTable的示例,但相同的内容可以应用于List。

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

答案 4 :(得分:0)

不要高举这个帖子,但每个人都建议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