我希望能够过滤包含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();
答案 0 :(得分:2)
Azerax的回答是新版RX的正确答案。
当您想要从UI元素中分离代码时,您可以:
input.ObserveOn(SynchronizationContext.Current).Subscribe(filterHandler, errorMsg);
这会将通知带回UI线程。否则,油门(*)将不起作用。
答案 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
});
使您的过滤算法更有效率。你是用StartWith或像Contains这样的东西过滤的吗?
您可以使用类似后缀树或列表项的所有前缀之类的内容并进行查找。但要准确描述你需要的东西,我会发现一些简单的东西 - 但足够有效。如果你想在ListBox中显示100.000项,但是如果你只需要 - 比如说100 - 那么UI很快(取消注释.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();
}
答案 2 :(得分:1)
首先,感谢@lasseespeholt,让我开始这个想法,对我来说很新。但确实Rx非常有趣,让生活更轻松:)
我必须使用包含节点(仅父级)的树视图实现类似的操作,这些节点由WinForms中的文本更改事件进行过滤。
由于一些奇怪的原因,该应用程序一直在崩溃。
我在MSDN网站上发现了一个PDF MSDN Rx(PDF 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。
答案 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