2010-08-04 107 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