Ответ 1
Вы можете использовать текстовое поле onChange()
. Если текст изменен в текстовом поле, проверьте, является ли введенное значение числом и вычисляет общее значение в соответствии с другим значением.
Есть ли способ в С# ждать, пока пользователь закончит вводить текст в текстовое поле, прежде чем принимать значения, которые они набрали, не нажав enter?
Немного изменил этот вопрос:
Хорошо, у меня есть простой калькулятор, который умножается на 2.
Вот что я хочу: пользователь вводит значение 1000 в текстовое поле и автоматически отображает 2000.
Вот что происходит: как только пользователь входит в 1, он умножается на 2 и выдает 2.
Вы можете использовать текстовое поле onChange()
. Если текст изменен в текстовом поле, проверьте, является ли введенное значение числом и вычисляет общее значение в соответствии с другим значением.
Я определяю "законченную типизацию" теперь как "пользователь набрал что-то, но не набрал ничего через определенное время". Имея это в качестве определения, я написал небольшой класс, который происходит из TextBox, чтобы продлить его на событие DelayedTextChanged
. Я не гарантирую, что это полно и без ошибок, но он удовлетворил небольшой smoke test. Не стесняйтесь изменять и/или использовать его. Я назвал его MyTextBox
, потому что сейчас я не мог найти лучшего имени. Вы можете использовать свойство DelayedTextChangedTimeout
для изменения ожидания ожидания. Значение по умолчанию - 10000 мс (= 10 секунд).
public class MyTextBox : TextBox
{
private Timer m_delayedTextChangedTimer;
public event EventHandler DelayedTextChanged;
public MyTextBox() : base()
{
this.DelayedTextChangedTimeout = 10 * 1000; // 10 seconds
}
protected override void Dispose(bool disposing)
{
if (m_delayedTextChangedTimer != null)
{
m_delayedTextChangedTimer.Stop();
if (disposing)
m_delayedTextChangedTimer.Dispose();
}
base.Dispose(disposing);
}
public int DelayedTextChangedTimeout { get; set; }
protected virtual void OnDelayedTextChanged(EventArgs e)
{
if (this.DelayedTextChanged != null)
this.DelayedTextChanged(this, e);
}
protected override void OnTextChanged(EventArgs e)
{
this.InitializeDelayedTextChangedEvent();
base.OnTextChanged(e);
}
private void InitializeDelayedTextChangedEvent()
{
if (m_delayedTextChangedTimer != null)
m_delayedTextChangedTimer.Stop();
if (m_delayedTextChangedTimer == null || m_delayedTextChangedTimer.Interval != this.DelayedTextChangedTimeout)
{
m_delayedTextChangedTimer = new Timer();
m_delayedTextChangedTimer.Tick += new EventHandler(HandleDelayedTextChangedTimerTick);
m_delayedTextChangedTimer.Interval = this.DelayedTextChangedTimeout;
}
m_delayedTextChangedTimer.Start();
}
private void HandleDelayedTextChangedTimerTick(object sender, EventArgs e)
{
Timer timer = sender as Timer;
timer.Stop();
this.OnDelayedTextChanged(EventArgs.Empty);
}
}
Если вы используете WPF и .NET 4.5 или более поздней версии, появляется новое свойство в части Binding элемента управления с именем "Delay". Он определяет промежуток времени, после которого источник обновляется.
<TextBox Text="{Binding Name, Delay=500}" />
Это означает, что источник обновляется только после 500 миллисекунд. Насколько мне известно, обновление после ввода TextBox закончилось. Btw. это свойство может быть полезно и в других сценариях, например. ListBox и т.д.
Другим простым решением было бы добавить таймер в вашу форму, установить для свойства Interval значение 250 и затем использовать событие таймера следующим образом:
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
Calculate(); // method to calculate value
}
private void txtNumber_TextChanged(object sender, EventArgs e)
{
timer1.Stop();
timer1.Start();
}
Вы можете обработать событие LostFocus текстового поля, которое будет срабатывать каждый раз, когда пользователь заканчивает ввод текста и удаляется от текстового поля. Вот документация по LostFocus: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.lostfocus.aspx
Однако я не уверен, что именно вы здесь пытаетесь сделать, поскольку вопрос о том, что означает "закончить", не очень ясен.
Я столкнулся с тем же вызовом, и вот мой простой подход. Это работает без проблем.
public partial class Form2 : Form
{
static int VALIDATION_DELAY = 1500;
System.Threading.Timer timer = null;
public Form2()
{
InitializeComponent();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox origin = sender as TextBox;
if (!origin.ContainsFocus)
return;
DisposeTimer();
timer = new System.Threading.Timer(TimerElapsed, null, VALIDATION_DELAY, VALIDATION_DELAY);
}
private void TimerElapsed(Object obj)
{
CheckSyntaxAndReport();
DisposeTimer();
}
private void DisposeTimer()
{
if (timer != null)
{
timer.Dispose();
timer = null;
}
}
private void CheckSyntaxAndReport()
{
this.Invoke(new Action(() =>
{
string s = textBox1.Text.ToUpper(); //Do everything on the UI thread itself
label1.Text = s;
}
));
}
}
В UWP я сделал отложенную проверку, сделав статический lastTimeOfTyping и проверив время, когда произошло событие "TextChanged". Это ожидает совпадения статического lastTimeOfTyping при совпадении нового времени "TextChanged", а затем выполняет требуемую функцию.
private const int millisecondsToWait = 500;
private static DateTime s_lastTimeOfTyping;
private void SearchField_OnTextChanged(object sender, TextChangedEventArgs e)
{
var latestTimeOfTyping = DateTime.Now;
var text = ((TextBox)sender).Text;
Task.Run(()=>DelayedCheck(latestTimeOfTyping, text));
s_lastTimeOfTyping = latestTimeOfTyping;
}
private async Task DelayedCheck(DateTime latestTimeOfTyping, string text)
{
await Task.Delay(millisecondsToWait);
if (latestTimeOfTyping.Equals(s_lastTimeOfTyping))
{
// Execute your function here after last text change
// Will need to bring back to the UI if doing UI changes
}
}
Как метод асинхронного расширения. Адаптировано из ответа Grecon14.
public static class UIExtensionMethods
{
public static async Task<bool> GetIdle(this TextBox txb)
{
string txt = txb.Text;
await Task.Delay(500);
return txt == txb.Text;
}
}
Использование:
if (await myTextBox.GetIdle()){
// typing has stopped, do stuff
}
Вы хотите использовать дескриптор Leave или LostFocus для рассматриваемого текстового поля. Я предполагаю, что вы используете WinForm, хотя вы не указываете его в своем вопросе.
Я не знаю, существует ли onChange() только в более старой версии С#, но я не могу его найти!
Следующие действия предназначены для обнаружения, когда пользователь либо нажимает клавишу Enter, либо выводит текст из TextBox, но только после изменения некоторого текста:
//--- this block deals with user editing the textBoxInputFile --- //
private Boolean textChanged = false;
private void textBoxInputFile_TextChanged(object sender, EventArgs e) {
textChanged = true;
}
private void textBoxInputFile_Leave(object sender, EventArgs e) {
if (textChanged) {
fileNameChanged();
}
textChanged = false;
}
private void textBoxInputFile_KeyDown(object sender, KeyEventArgs e) {
if (textChanged & e.KeyCode == Keys.Enter) {
fileNameChanged();
}
textChanged = false;
}
//--- end block --- //
В идеале решение для наследования, такое как esskars, является подходящим решением, но оно не очень подходит для дизайнера, поэтому для повторного использования я выбрал вспомогательный класс вспомогательного стиля:
using System;
using System.Threading;
using System.Windows.Forms;
using Timer = System.Threading.Timer;
internal class DelayedText : IDisposable
{
private readonly EventHandler _onTextChangedDelayed;
private readonly TextBox _textBox;
private readonly int _period;
private Timer _timer;
public DelayedText(TextBox textBox, EventHandler onTextChangedDelayed, int period = 250)
{
_textBox = textBox;
_onTextChangedDelayed = onTextChangedDelayed;
_textBox.TextChanged += TextBoxOnTextChanged;
_period = period;
}
public void Dispose()
{
_timer?.Dispose();
_timer = null;
}
private void TextBoxOnTextChanged(object sender, EventArgs e)
{
Dispose();
_timer = new Timer(TimerElapsed, null, _period, Timeout.Infinite);
}
private void TimerElapsed(object state)
{
_onTextChangedDelayed(_textBox, EventArgs.Empty);
}
}
Использование в конструкторе формы:
InitializeComponent();
...
new DelayedText(txtEdit, txtEdit_OnTextChangedDelayed);
Я не пнул это сильно, но, кажется, работает на меня.
Что делать, если вы вызываете событие на основе нажатия клавиши или возврата?
Мой коллега предложил решение с использованием Rx и регулирования событий:
var FindDelay = 500;//milliseconds
//textBox is your text box element
Observable.FromEventPattern<EventArgs>(textBox, "TextChanged")
.Select(ea => ((TextBox) ea.Sender).Text)
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliseconds(FindDelay))
.Subscribe(text => {
//your handler here
});
Если пользователь печатает быстро и вы хотите отложить расчет до тех пор, пока он не перестанет печатать, может помочь следующий код:
private void valueInput_TextChanged(object sender, EventArgs e)
{
CalculateAfterStopTyping();
}
Thread delayedCalculationThread;
int delay = 0;
private void CalculateAfterStopTyping()
{
delay += 200;
if (delayedCalculationThread != null && delayedCalculationThread.IsAlive)
return;
delayedCalculationThread = new Thread(() =>
{
while (delay >= 200)
{
delay = delay - 200;
try
{
Thread.Sleep(200);
}
catch (Exception) {}
}
Invoke(new Action(() =>
{
// do your calcualation here...
}));
});
delayedCalculationThread.Start();
}