2010-08-04 6 views
5

Ich möchte in der Lage sein, eine Listbox mit 1000 Strings, jeweils 50 - 4000 Zeichen Länge, zu filtern, wie der Benutzer in das Textfeld ohne Verzögerung eingibt.Echtzeitfilterung von Listbox

Ich verwende derzeit einen Timer, der die Listbox aktualisiert, nachdem das TextChanged Ereignis des Textfelds nicht in 300ms ausgelöst wurde. Dies ist jedoch ziemlich ruckelig und die UI friert manchmal kurz ein.

Was ist der normale Weg, ähnliche Funktionen zu implementieren?

Edit: Ich verwende winforms und .net2.

Dank

wird hier eine abgespeckte Version des Codes Ich bin derzeit mit:

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 oder WinForms oder etwas anderes? – kbrimington

+0

Ich benutze Winforms. – Ryan

Antwort

1

Sie zwei Dinge tun:

  1. Machen Sie mit einem reaktions UI zweiter Thread, der sich um die Filterung kümmert. Eine wirklich großartige neue Technologie ist Reaktive Erweiterungen (Rx), die genau das tun werden, was Sie brauchen.

    Ich kann ein Beispiel geben. Ich nehme an, Sie verwenden WinForms? Ein Teil von dir Code würde helfen.

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

    Hier ist ein kleiner Vorgeschmack:

    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. Machen Sie Ihre Filteralgorithmus effizienter. Filtern Sie mit etwas wie StartWith oder etwas wie Enthält?

    Sie können etwas wie eine Suffixstruktur oder alle Präfixe der Listenelemente verwenden und eine Suche durchführen. Aber beschreibe, was du genau brauchst und ich werde etwas Einfaches finden - aber effizient genug. Die Benutzeroberfläche ist ziemlich schwer, wenn Sie 100.000 Objekte in der ListBox anzeigen möchten, aber wenn Sie nur - sagen wir 100 - nehmen, ist es schnell (entfernen Sie die Zeile .Take (100)). Es kann auch etwas besser gemacht werden, wenn die Suche in einem anderen Thread erfolgt. Es sollte mit Rx einfach sein, aber ich habe es nicht versucht.

aktualisieren

so etwas wie dies versuchen. Es funktioniert hier mit 100.000 Elementen, die ~ 10 Zeichen lang sind. Es verwendet Reaktive Erweiterungen (der Link zuvor).

Auch der Algorithmus ist naiv und kann viel schneller gemacht werden, wenn Sie möchten.

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

Danke für die Antwort. Ich habe meine Frage mit einer verkürzten Version meines Codes aktualisiert. – Ryan

+0

Vielen Dank für den Beispielcode. Soweit ich das beurteilen kann, sind die reaktiven Erweiterungen nur für .net3.5 und höher verfügbar. Gibt es irgendwelche .net2 Äquivalente, die ich verwenden könnte? – Ryan

+0

Hhm so ziemlich alles muss neu geschrieben werden: D LINQ ist nicht in .Net Framework 2.0 entweder. Kann ich den alten Verzögerungstimer sehen, den du geschrieben hast? Wenn es genug ist, könnten Sie schneiden, so dass nur die ersten 100 in Ihrer Ergebnisliste Items.AddRange zur Verfügung gestellt wird - es wird richtig viel schneller als wenn Sie 1000 Elemente anzeigen möchten. –

1

Zunächst einmal, dank @lasseespeholt, für das Erhalten begann, mich auf diese Idee, mir sehr neu. Aber in der Tat ist Rx sehr interessant zu tun, macht das Leben viel einfacher :)

Ich musste eine ähnliche Sache mit einer Baumansicht implementieren, die Knoten (nur Elternebene) enthält, die durch das Textänderungsereignis in WinForms gefiltert werden.

Die App stürzte aus irgendeinem Grund auf mich ab.

Ich habe eine PDF auf der MSDN-Website @MSDN Rx (PDF download link - siehe Seite 25) gefunden, die ein ähnliches Problem angriff und ein Problem mit Cross-Thread-Zugriff beschrieben hatte.

Hier ist die Lösung, die es für mich funktionierte, die Lösung ist, auch vor dem abonnieren ObserveOn zu verwenden.

Hier ist Beispielcode, der die neuere Version von Rx verwendet - 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 Antwort ist die richtige mit der neuen Version von RX.

Wenn Sie den Code aus dem UI-Elemente zu trennen, können Sie haben:

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

Dies wird die Meldung zurück an den UI-Thread bringen. Andernfalls wird die Drossel (*) nicht wirksam.

0

Dieser Thread sollte nicht hochgejubelt werden, aber jeder schlug LINQ-ähnliche Entwicklungen oder zusätzliche Ressourcen vor, um den Bibliotheksaufwand der Anwendung zu erhöhen.

Was ich getan habe, war eine List (Of) -Auflistung zu definieren, um die ursprüngliche Liste von Informationen, die ich in die ListBox geladen habe, und eine Filtered List (Of) -Auflistung aufzunehmen, um die resultierende gefilterte Teilmenge zu speichern.

Ich habe den RegEx-Namespace verwendet, um die Filterung durchzuführen, aber Sie könnten das mit dem String-Framework inhärente Mustersystem verwenden. Hier ist der Code, mit dem ich den Job erledigt habe.

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