2010-08-04 78 views
5

我希望能够过滤包含1000个字符串的列表框,每个字符长度为50-4000个字符,因为用户在文本框中输入时没有延迟。实时过滤列表框

我正在使用一个计时器,它在文本框的TextChanged事件没有在300ms内被触发后更新列表框。然而,这是非常生涩的,用户界面有时会暂时冻结。

实现与此类似功能的正常方式是什么?

编辑:我使用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(); 
+0

ASP.NET或WinForms或其他? – kbrimington 2010-08-04 20:18:33

+0

我正在使用winforms。 – Ryan 2010-08-04 20:18:51

回答

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之类的东西来过滤?

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

更新

尝试这样的事情。它在这里工作很好,有10万个字符长的100.000个元素。它使用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

感谢您的回复。我用我的代码的精简版本更新了我的问题。 – Ryan 2010-08-04 20:44:35

+0

非常感谢您的示例代码。但是,据我所知,被动扩展只适用于.net3.5及更高版本。有没有我可以使用的.net2等价物? – Ryan 2010-08-04 21:37:23

+0

几乎所有的东西都必须重写:D LINQ也不在.Net框架2.0中。我可以看到你写的旧延时计时器吗?如果它足够了,那么可以剪切,只有结果列表中的前100个提供给Items.AddRange - 它将比显示1000个元素的速度快很多。 – 2010-08-04 22:03:28

1

首先,感谢@lasseespeholt,你为我开始对这个想法很新的给我。但确实Rx是非常有趣的事情,使生活更容易:)

我不得不实施一个类似的东西,包含节点(只有父级)由WinForms中的文本更改事件过滤的树视图。

由于某种奇怪的原因,该应用程序不断崩溃。

我在MSDN站点@MSDN RxPDF download link - 请参阅第25页)上找到PDF,它正在解决类似问题并描述了跨线程访问问题。

下面是它为我提供的修复程序,解决方案是在订阅之前也使用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); 
    } 
2

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

当你想单独从UI元素的代码,你可以有:

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

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

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