连续输入时不要提升TextChanged
我有一个文本框,有一个相当沉重的_TextChanged
事件处理程序。 在正常的打字条件下,性能还可以,但是当用户执行长时间的连续动作时,它会明显滞后,例如按下退格按钮一次删除大量文本。
例如,事件花费0.2秒完成,但用户每0.1秒执行一次删除。 因此,它无法赶上,并且会有积压的事件需要处理,导致UI滞后。
但是,事件不需要为这些中间状态运行,因为它只关心最终结果。 有没有办法让事件处理程序知道它应该只处理最新的事件,并忽略所有以前陈旧的变化?
根据经验发现这个解决方案简单而且整洁到目前为止,我已经多次遇到过这个问题。 它在Windows Form
工作,但可以很容易地转换为WPF
。
这个怎么运作:
当TypeAssistant
一个对象被告知发生了text change
,它将运行一个计时器。 然后在WaitingMilliSeconds
之后,计时器将引发Idle
事件。 通过处理此事件,您可以完成工作。 如果在此期间发生另一个text change
,则计时器将重置。
public class TypeAssistant { public event EventHandler Idled = delegate { }; public int WaitingMilliSeconds { get; set; } System.Threading.Timer waitingTimer; public TypeAssistant(int waitingMilliSeconds = 600) { WaitingMilliSeconds = waitingMilliSeconds; waitingTimer = new Timer(p => { Idled(this, EventArgs.Empty); }); } public void TextChanged() { waitingTimer.Change(WaitingMilliSeconds, System.Threading.Timeout.Infinite); } }
用法:
public partial class Form1 : Form { TypeAssistant assistant; public Form1() { InitializeComponent(); assitant = new TypeAssistant(); assitant.Idled += assitant_Idled; } void assistant_Idled(object sender, EventArgs e) { this.Invoke( new MethodInvoker(() => { // do your job here })); } private void yourFastReactingTextBox_TextChanged(object sender, EventArgs e) { assistant.TextChanged(); } }
好处:
- 简单!
- 在
WPF
和Windows Form
- 使用.Net Framework 3.5+
缺点:
- 再运行一个线程
- 需要调用而不是直接操纵表单
我也认为Reactive Extensions是去这里的方式。 我的查询略有不同。
我的代码如下所示:
IDisposable subscription = Observable .FromEventPattern( h => textBox1.TextChanged += h, h => textBox1.TextChanged -= h) .Select(x => textBox1.Text) .Throttle(TimeSpan.FromMilliseconds(300)) .Select(x => Observable.Start(() => /* Do processing */)) .Switch() .ObserveOn(this) .Subscribe(x => textBox2.Text = x);
现在这正是你期待的方式。
FromEventPattern
将TextChanged
转换为一个返回sender和event args的observable。 Select
然后将它们更改为TextBox
的实际文本。 如果在300
毫秒内发生新的击键,则Throttle
基本上忽略了之前的击键 – 因此只传递在滚动的300
毫秒窗口内按下的最后一次击键。 Select
然后调用处理。
现在,这是魔术。 Switch
做了一些特别的事情。 由于select返回了一个observable,我们在Switch
之前有一个IObservable
。 Switch
只接受最新生成的observable并从中生成值。 这非常重要。 这意味着如果用户在现有处理运行时键入击键,它将在结束时忽略该结果,并且只报告最新运行处理的结果。
最后有一个ObserveOn
将执行返回给UI线程,然后是Subscribe
实际处理结果 – 在我的例子中更新第二个TextBox
上的TextBox
。
我认为这段代码非常简洁,非常强大。 您可以使用Nuget为“Rx-WinForms”获取Rx。
您可以将事件处理程序标记为async
并执行以下操作:
bool isBusyProcessing = false; private async void textBox1_TextChanged(object sender, EventArgs e) { while (isBusyProcessing) await Task.Delay(50); try { isBusyProcessing = true; await Task.Run(() => { // Do your intensive work in a Task so your UI doesn't hang }); } finally { isBusyProcessing = false; } }
尝试使用try-finally
子句是为了确保isBusyProcessing
在某些时候被保证设置为false
,这样你就不会在无限循环中结束。
一种简单的方法是在内部方法或委托上使用async / await:
private async void textBox1_TextChanged(object sender, EventArgs e) { // this inner method checks if user is still typing async Task UserKeepsTyping() { string txt = textBox1.Text; // remember text await Task.Delay(500); // wait some return txt != textBox1.Text; // return that text chaged or not } if (await UserKeepsTyping()) return; // user is done typing, do your stuff }
此处不涉及线程。 对于早于7.0的C#版本,您可以声明委托:
Func> UserKeepsTyping = async delegate () {...}
请注意,此方法不会使您偶尔处理相同的“结束重新连接”两次。 例如,当用户键入“ab”,然后立即删除“b”时,您可能最终处理“a”两次。 但这些场合应该是罕见的。 为了避免它们,代码可能是这样的:
// last processed text string lastProcessed; private async void textBox1_TextChanged(object sender, EventArgs e) { // clear last processed text if user deleted all text if (string.IsNullOrEmpty(textBox1.Text)) lastProcessed = null; // this inner method checks if user is still typing async Task UserKeepsTyping() { string txt = textBox1.Text; // remember text await Task.Delay(500); // wait some return txt != textBox1.Text; // return that text chaged or not } if (await UserKeepsTyping() || textBox1.Text == lastProcessed) return; // save the text you process, and do your stuff lastProcessed = textBox1.Text; }
Reactive Extensions非常适合处理这种情况。
因此,您希望通过将其限制0.1
秒来捕获TextChanged
事件并处理输入。 您可以将TextChanged
事件转换为IObservable
并订阅它。
像这样的东西
(from evt in Observable.FromEventPattern(textBox1, "TextChanged") select ((TextBox)evt.Sender).Text) .Throttle(TimeSpan.FromMilliSeconds(90)) .DistinctUntilChanged() .Subscribe(result => // process input);
因此,这段代码订阅TextChanged
事件,限制它,确保只获得不同的值,然后从事件args中提取Text
值。
请注意,此代码更像是伪代码,我没有测试它。 要使用Rx Linq
,您需要安装Rx-Linq Nuget包 。
如果您喜欢这种方法,可以查看这篇实现使用Rx Linq自动完成控制的博客文章 。 我还建议Bart Re Smet在Reactive Extensions上的精彩演讲 。
使用TextChanged与焦点检查和TextLeave的组合。
private void txt_TextChanged(object sender, EventArgs e) { if (!((TextBox)sender).Focused) DoWork(); } private void txt_Leave(object sender, EventArgs e) { DoWork(); }
我不知道如何剔除事件队列,但我可以想到两种方法可以解决这个问题。
如果你想要一些快速的东西(并且根据某些人的标准略微变脏),你可以引入一个排序的等待计时器 – 当validation函数运行时,用当前时间设置一个标志(函数内的静态变量应该足够)。 如果在上次运行和完成的0.5秒内再次调用该函数,则立即退出该函数(大大减少函数的运行时间)。 这将解决积压的事件,前提是它是导致它缓慢而不是触发事件本身的函数的内容。 这样做的缺点是你必须引入某种备份检查以确保当前状态已经过validation – 即,如果最后一次更改发生在0.5s块发生时。
或者,如果您唯一的问题是您不希望在用户进行连续操作时进行validation,则可以尝试修改事件处理程序,以便在按键正在进行时退出而不执行validation,或者甚至可能将validation操作绑定到KeyUp而不是TextChanged。
有很多方法可以实现这一目标。 例如,如果对特定键执行KeyDown事件(例如,对于您的示例执行退格键,但理论上您应该将其扩展为将键入字符的任何内容),validation函数将在不执行任何操作的情况下退出,直到KeyUp事件为止。同一把钥匙被解雇了。 这样它就不会运行,直到最后一次修改……希望如此。
这可能不是达到预期效果的最佳方式(它可能根本不起作用!在用户完成按键操作之前,_TextChanged事件可能会触发),但理论是合理的。 没有花一些时间玩游戏我不能完全确定按键的行为 – 你可以检查按键是否被按下并退出,或者你是否必须手动举起一个标志,这在KeyDown和KeyUp之间是正确的? 一点点玩你的选择应该很清楚你的特定情况的最佳方法是什么。
我希望有所帮助!
你不能沿着以下几行做点什么吗?
Stopwatch stopWatch; TextBoxEnterHandler(...) { stopwatch.ReStart(); } TextBoxExitHandler(...) { stopwatch.Stop(); } TextChangedHandler(...) { if (stopWatch.ElapsedMiliseconds < threshHold) { stopwatch.Restart(); return; } { //Update code } stopwatch.ReStart() }
private async Task ValidateText() { if (m_isBusyProcessing) return; // Don't validate on each keychange m_isBusyProcessing = true; await Task.Delay(200); m_isBusyProcessing = false; // Do your work here. }
- C# – Parallel.Invoke和Parallel.ForEach本质上是一回事吗?
- Visual Studio 2010不断更改我的winforms控件
- 网络共享上的.NET 4.0应用程序导致SecurityException
- parellellizing异步调用时如何获得最大出站请求?
- DirectoryInfo.EnumerateFiles(…)导致UnauthorizedAccessException(和其他exception)
- .NET 4 ObjectCache – 我们可以陷入“缓存过期”事件吗?
- 为什么直接转换失败但“as”运算符在测试约束generics类型时成功?
- 简单的Linq表达式将无法编译
- 我应该如何禁用每个对象的Entity Framework表引用(外部)列表?