Est-il possible, en C #, d’attendre que l’utilisateur ait fini de taper dans une zone de texte avant de saisir les valeurs qu’il a tapées sans appuyer sur Entrée?
A révisé cette question un peu:
D'accord, j'ai une calculatrice simple qui multiplie par 2.
Voici ce que je veux qu'il fasse: l'utilisateur entre une valeur telle que 1000 dans une zone de texte et il en affiche automatiquement 2000.
Voici ce qui se passe: Dès que l'utilisateur entre 1, il multiplie par 2 et en sort 2.
Vous pouvez utiliser l’événement textbox onChange()
. Si le texte est modifié dans la zone de texte, vérifiez si la valeur entrée est un nombre et calculez la valeur totale en fonction de l'autre valeur.
Je définis maintenant "fini de taper" comme "l'utilisateur a tapé quelque chose mais n'a rien tapé après un certain temps". Ayant cela comme définition, j'ai écrit une petite classe dérivée de TextBox pour l'étendre d'un événement DelayedTextChanged
. Je ne m'assure pas qu'il est complet et sans bug, mais cela satisfait à un petit test de fumée. N'hésitez pas à changer et/ou à l'utiliser. Je l'ai appelé MyTextBox
parce que je ne pouvais pas trouver un meilleur nom pour le moment. Vous pouvez utiliser la propriété DelayedTextChangedTimeout
pour modifier le délai d'attente. La valeur par défaut est 10000ms (= 10 secondes).
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);
}
}
Une autre solution simple consiste à ajouter un minuteur à votre formulaire, à définir la propriété Interval sur 250, puis à utiliser l'événement tick du minuteur comme suit:
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();
}
Si vous utilisez WPF et .NET 4.5 ou version ultérieure, il existe une nouvelle propriété dans la partie Liaison d'un contrôle nommé "Délai". Il définit une période après laquelle la source est mise à jour.
<TextBox Text="{Binding Name, Delay=500}" />
Cela signifie que la source est mise à jour seulement après 500 millisecondes. Autant que je sache, il fait la mise à jour après avoir tapé dans la zone de texte terminée. Btw. cette propriété peut également être utile dans d'autres scénarios, par exemple. ListBox etc.
Vous pouvez gérer l'événement LostFocus de la zone de texte qui se déclenchera chaque fois que l'utilisateur finit de taper et navigue en dehors de la zone de texte. Voici la documentation sur LostFocus: http://msdn.Microsoft.com/en-us/library/system.windows.forms.control.lostfocus.aspx
Cependant, je ne suis pas sûr de ce que vous essayez exactement de faire ici car la question n'est pas très claire sur ce que signifie "finir".
J'ai relevé le même défi et voici mon approche simple. Cela fonctionne sans problèmes.
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;
}
));
}
}
Dans UWP, j'ai effectué une vérification différée en effectuant un lastTimeOfTyping statique et en vérifiant l'heure à laquelle l'événement "TextChanged" s'est produit. Cela attend que la dernière constante timeTimeOfTyping corresponde à la nouvelle correspondance "TextChanged", puis à l'exécution de la fonction souhaitée.
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
}
}
Je ne sais pas si onChange () n'existe que dans une ancienne version de c #, mais je ne le trouve pas!
Les opérations suivantes permettent de détecter lorsqu'un utilisateur appuie sur la touche Entrée ou sur des onglets en dehors de la zone de texte, mais uniquement après avoir modifié du texte:
//--- 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 --- //
Un de mes collègues a suggéré une solution utilisant Rx et la limitation d'événements:
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
});
En tant que méthode d'extension asynchrone. Adapté de la réponse de 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;
}
}
Usage:
if (await myTextBox.GetIdle()){
// typing has stopped, do stuff
}
Que se passe-t-il si vous déclenchez un événement basé sur une frappe telle que tabulation ou retour?
Idéalement, une solution d’héritage telle que celle d’esskar est la solution, mais elle ne fonctionne pas bien avec le concepteur. Pour obtenir la réutilisation, j’ai opté pour une classe secondaire de type aide:
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);
}
}
Utilisation, dans le constructeur de formulaire:
InitializeComponent();
...
new DelayedText(txtEdit, txtEdit_OnTextChangedDelayed);
Je n'ai pas donné de coups de pied, mais cela semble fonctionner pour moi.