C# Don';在连续键入时,不能更改raise TEXT
我有一个文本框,它有一个相当大的C# Don';在连续键入时,不能更改raise TEXT,c#,winforms,.net-4.0,C#,Winforms,.net 4.0,我有一个文本框,它有一个相当大的\u TextChanged事件处理程序。在正常的输入条件下,性能是可以的,但是当用户执行长时间的连续操作时,性能会明显滞后,例如按住backspace按钮一次删除大量文本 例如,事件需要0.2秒才能完成,但用户每0.1秒执行一次删除。因此,它无法迎头赶上,需要处理的事件将积压,从而导致UI滞后 但是,事件不需要在状态之间运行,因为它只关心最终结果。有没有办法让事件处理程序知道它应该只处理最新的事件,而忽略以前所有过时的更改?您可以将事件处理程序标记为async,
\u TextChanged
事件处理程序。在正常的输入条件下,性能是可以的,但是当用户执行长时间的连续操作时,性能会明显滞后,例如按住backspace按钮一次删除大量文本
例如,事件需要0.2秒才能完成,但用户每0.1秒执行一次删除。因此,它无法迎头赶上,需要处理的事件将积压,从而导致UI滞后
但是,事件不需要在状态之间运行,因为它只关心最终结果。有没有办法让事件处理程序知道它应该只处理最新的事件,而忽略以前所有过时的更改?您可以将事件处理程序标记为
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
Try finally
子句是必需的,以确保在某个点将isBusyProcessing
设置为false
,这样您就不会陷入无限循环。我不知道如何剔除事件队列,但我可以想出两种方法来处理这个问题
如果您想要一些快速的东西(按照某些人的标准,它有点脏),您可以引入一个等待计时器——当验证函数运行时,用当前时间设置一个标志(函数中的静态变量应该足够了)。如果在上次运行并完成后的0.5秒内再次调用该函数,请立即退出该函数(大大缩短函数的运行时间)。这将解决事件积压问题,前提是函数的内容导致它变慢,而不是事件本身的触发。这样做的缺点是,您必须引入某种备份检查,以确保当前状态已被验证,即,如果上次更改发生在0.5s块发生时
或者,如果您唯一的问题是不希望在用户进行连续操作时进行验证,您可以尝试修改事件处理程序,使其在按键进行时不执行验证而退出,或者甚至可以将验证操作绑定到KeyUp而不是TextChanged
有很多方法可以实现这一点。例如,如果对特定的键执行KeyDown事件(例如,您的示例中使用backspace,但理论上您应该将其扩展到任何将键入字符的项),则验证函数将不执行任何操作而退出,直到触发同一键的keydup事件。这样,直到最后一次修改完成后它才会运行。。。希望如此
这可能不是达到预期效果的最佳方式(它可能根本不起作用!有可能在用户按完键之前触发_TextChanged事件),但理论是正确的。如果不花一些时间玩,我不能绝对确定按键的行为-你能检查一下按键是否被按下并退出,或者你是否必须手动升起一个在KeyDown和KeyUp之间为真的标志?稍微玩弄一下你的选择,就可以清楚地知道什么是适合你的特定情况的最佳方法
我希望这有帮助 我已经多次遇到这个问题,根据我自己的经验,到目前为止,我发现这个解决方案简单明了。它基于
Windows窗体
,但可以轻松转换为WPF
工作原理:
当TypeAssistant
得知发生了文本更改时,它会运行计时器。在等待毫秒后
计时器引发空闲
事件。通过处理此事件,您可以执行任何您想要的工作(例如处理输入的tex)。如果在计时器启动和等待毫秒后的时间范围内发生另一次文本更改
,计时器将重置
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();
assistant = new TypeAssistant();
assistant.Idled += assistant_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窗体中工作
- 使用.NETFramework 3.5+
缺点:
- 再运行一个线程
- 需要调用而不是直接操纵表单
我们正在很好地处理这种情况
因此,您希望通过限制
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
事件,对其进行限制,确保只获取不同的值,然后从事件参数中提取Text
值
请注意,这个代码更像是一个伪代码,我没有测试它。
要使用Rx Linq
,您需要安装
如果您喜欢这种方法,可以使用Rx Linq检查是否实现了自动完成控制。我还建议您使用反应式扩展。您不能按照下面的思路做一些事情吗
Stopwatch stopWatch;
TextBoxEnterHandler(...)
{
stopwatch.ReStart();
}
TextBoxExitHandler(...)
{
stopwatch.Stop();
}
TextChangedHandler(...)
{
if (stopWatch.ElapsedMiliseconds < threshHold)
{
stopwatch.Restart();
return;
}
{
//Update code
}
stopwatch.ReStart()
}
秒表秒表;
TextBoxEnterHandler(…)
{
stopwatch.ReStart();
}
TextBoxExitHandler(…)
{
秒表;
}
TextChangedHandler(…)
{
if(stopWatch.ElapsedMiliseconds
我还认为,反应式扩展是解决这一问题的方法。不过,我有一个稍微不同的问题
我的代码如下所示:
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);
// 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<bool> 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;
}
现在,这正是您预期的工作方式
FromEventPattern
将TextChanged
转换为可观察对象,返回发送方和事件参数
private async void textBox1_TextChanged(object sender, EventArgs e) {
// this inner method checks if user is still typing
async Task<bool> 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
}
Func<Task<bool>> UserKeepsTyping = async delegate () {...}
// 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<bool> 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;
}
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.
}
string mostRecentText = "";
async void entry_textChanged(object sender, EventArgs e)
{
//get the entered text
string enteredText = (sender as Entry).Text;
//set the instance variable for entered text
mostRecentText = enteredText;
//wait 1 second in case they keep typing
await Task.Delay(1000);
//if they didn't keep typing
if (enteredText == mostRecentText)
{
//do what you were going to do
doSomething(mostRecentText);
}
}
private List<Task<bool>> taskTypeQueue = new List<Task<bool>>();
private async void textBox_TextChanged(object sender, EventArgs e)
{
async Task<bool> isStillTyping()
{
Application.DoEvents();
int taskCount = taskTypeQueue.Count;
string oldStr = textBox.Text;
await Task.Delay(1500);
if ((oldStr != textBox.Text) || (taskCount != taskTypeQueue.Count - 1))
{
return true;
}
return false;
}
taskTypeQueue.Add(isStillTyping());
if (await taskTypeQueue[taskTypeQueue.Count - 1])
return;
// typing appears to have stopped, continue
taskTypeQueue.Clear();
}