C#等待用户在文本框中输入完成

39

在C#中,有没有一种方法可以等待用户在文本框中完成输入后再获取他们键入的值而无需按Enter键?

稍微修改了这个问题:

好的,我有一个简单的乘2计算器。

这是我想要它做的事情:用户将一个值(例如1000)输入到文本框中,它会自动显示2000。

这是发生的事情:当用户输入1时,它就立即乘以2并输出2。


19
您如何确定他们已经“完成输入”?我认为至少还需要几个版本的时间,C#才会添加用于心灵感应事件的处理程序... - Donut
3
问题在于您需要定义“完成”的意思。它是当他们停止输入3秒钟,5秒钟等等吗...最简单的方法是使用像回车或点击按钮这样的标记。 - JaredPar
7
我怀疑这样的系统如果我妈妈在使用它时会出现误报 - 很难知道她是否已经完成打字,还是在寻找下一个字母... - Widor
@user990951 你无法确定何时停止监听键入... 因为他们随时可能会键入,而没有指示器告诉你他们已经完成。你需要一些事件,比如按钮点击或用户按下Enter键... 一些信号来表明他们已经完成输入。 - Matthew Cox
显示剩余2条评论
17个回答

58

我现在将"完成输入"定义为"用户已经输入了一些内容,但在一段时间后未继续输入任何内容"。以此为定义,我编写了一个从TextBox派生的小类,通过DelayedTextChanged事件来扩展它。我不能保证它完整且没有bug,但经过了简单的测试。请随意更改和/或使用它。我将其称为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);
    }
}

23

另一个简单的解决方案是在表单中添加计时器,将Interval属性设置为250,然后使用计时器的Tick事件,如下:

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();
}

22
如果您正在使用WPF和.NET 4.5或更高版本,则控件的绑定部分有一个名为“Delay”的新属性。它定义了源更新之后的时间间隔。
<TextBox Text="{Binding Name, Delay=500}" />

这意味着源仅在500毫秒后更新。就我所看到的,它在输入框结束输入后才进行更新。顺便说一下,这个属性在其他场景中也很有用,比如ListBox等。


1
如果你想在输入时更新源,那么在绑定中还需要加上 UpdateSourceTrigger=PropertyChanged - MgSam

8

我也面对了同样的挑战,以下是我的简单解决方案。这种方法可以正常工作。

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; 
            }
                ));            
        }
    }

7

1
我喜欢这个解决方案,但它不能确定文本框文本是否已更改。因此,唯一协调的方式是使用TextChanged来引发一个标志(布尔值),然后在LostFocus事件中,如果标志为真,则执行您需要执行的操作。使用计时器和延迟只会导致疯狂的编码。 - Nandostyle

5
在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
        }
    }

4
作为异步扩展方法。 改编自Grecon14的答案。
注意:这缺乏对光标位置更改的考虑,因此如果用户使用箭头键移动但实际上并未更改文本,则会返回true。 问题说明“完成打字”,我不确定移动光标是否构成实际打字,也许? 作为用户,我希望它能包括这种活动。 不幸的是,为了实现适当的界面功能,它需要比以下更复杂。 如果需要,可以参见SurfingSanta的答案,其中有一个keydown订阅。
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
}

2

我不知道onChange()是否只存在于旧版本的c#中,但我找不到它!

以下内容可用于检测用户何时按下Enter键或离开文本框,但仅在更改了一些文本后才有效:

    //--- 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  --- //

1

您可以使用文本框的 onChange() 事件。如果文本框中的文本已更改,请检查输入的值是否为数字,并根据其他值计算总值。


4
onChange() 不是在 VS2015 的 C# 中针对文本框的事件。 - Jhollman
14
@Jhollman,我在2011年回答了这个问题。那时还没有Visual Studio 2015。 - evilone
3
这句话的意思是什么?为什么这个被选为答案? - Krythic
@Krythic,这是2011年的一个正确答案,并且被广泛接受。我猜在Visual Studio 2015(及以后版本)中,事件onChange()被称为TextChanged()。 - Lati
如果你正在寻找这个问题的最新答案,我相信它是捕获“离开”事件。 - MX313
唯一和谐的方法是使用TextChanged来引发一个标志(布尔值),然后在失去焦点事件中,如果标志为真,则执行您需要执行的操作。使用计时器和延迟只会导致疯狂的编码。 - Nandostyle

1

你想要处理文本框的LeaveLostFocus事件。我假设你在使用WinForm,尽管你在问题中没有明确说明。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接