为什么这不会导致事件的无限循环?

36

我有一个简单的应用程序,可以将输入到其中一个文本框的任何文本在另一个文本框中反转显示。而且,你可以修改任意一个文本框,这些更改也会(字面上)反映在另一个文本框中。

我写了这段代码,认为它会引起问题。

private void realText_TextChanged(object sender, EventArgs e)
{
    mirrorText.Text = mirror(realText.Text);
}

private void mirrorText_TextChanged(object sender, EventArgs e)
{
    realText.Text = mirror(mirrorText.Text);
}

private string mirror(string text)
{
    return new string(text.Reverse().ToArray()).Replace("\n\r", "\r\n");
}

我接着尝试了它,因为我认为它会导致一个无限循环(realText 改变 mirrorText,另一个事件发生,mirrorText 改变 realText,以此类推)。然而,除了预期的行为外,什么都没有发生。

当然,我对此感到非常高兴,我可以把它留在这里。 但我真的可以吗?

我非常确定 TextChanged 事件应该在每次更改 Text 时触发。这是事件中某种错误保护的意图,还是我只是幸运而已?这段代码是否会在其他计算机、其他构建设置等情况下出现问题?这可以轻松解决:

private void realText_TextChanged(object sender, EventArgs e)
{
    if (realText.Focused)
    {
        mirrorText.Text = Mirror(realText.Text);
    }
}

为了安全起见,我可能会去做这件事,但是有必要检查吗?(我甚至不会问是否推荐。)


3
我相信 TextChanged 事件只有在文本从 UI 上改变时才会触发,而不是从代码中改变。 - Magnus
5
如果通过编程修改或用户交互方式改变文本属性,就会引发此事件。@Magnus from MSDN - Marton
@Magnus,你可以使用Reflector(我想那就是它的名字,对吧?或者JetBrains的那个...嗯...我记不起来名字了)来检查和查看! - Daren Thomas
11
如果将“Text”属性设置为与当前值完全相同,可能不会触发“TextChanged”事件,因为实际上并没有进行更改。 - user743382
@hvd 我会假设这并不总是情况,并在我的代码中添加我在问题中包含的解决方法。 - PurkkaKoodari
显示剩余6条评论
4个回答

31
根据评论和已经回答的内容,当您将

22

它不会导致循环的原因是它检查了Text属性是否实际更改,即新值是否不等于旧值。在您的情况下,mirror函数恰好相反,这导致两次传递后文本相同。


1
@Pietu1998(以及@Basic):感谢您的编辑。我不得不推迟自己的编辑,当我回来时,发现它已经包含了两个完美的编辑。 - Willem van Rumpt
很抱歉,但我会接受[user:hvd]的回答,因为他们首先提出了这个建议并发布了更详细的答案。尽管如此,我还是很感谢你的回答。 - PurkkaKoodari
1
@Pietu1998:没问题 :) - Willem van Rumpt

4
很容易检查。 首先,用两个文本框控件替换。
    class T : TextBox
    {
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                base.Text = value;
            }
        }
    }

第二步,设置setter的断点。将以下表达式添加到观察窗口:
  • Name
  • Text
  • value
第三步,启动应用程序,从其他地方复制“123”并将其粘贴到第一个文本框中。然后就可以了:
第一次断点:
  • Name:“mirrorText”
  • Text:“”
  • value:“321”
第二次断点:
  • Name:“realText”
  • Text:“123”
  • value:“123”
第三次……哎呀,它不再断点了。要检测原因,我们必须深入研究。看看referencesource:text box setter does nothing不寻常,但TextBoxBase的看起来很有趣
        set {
            if (value != base.Text) { // Gotcha!
                base.Text = value;
                if (IsHandleCreated) {
                    // clear the modified flag
                    SendMessage(NativeMethods.EM_SETMODIFY, 0, 0);
                }
            }
        }

所以,就像hvd已经回答的那样,原因是如果旧值和新值相同,文本框不会引发TextChanged事件。我认为这种行为不会改变,至少对于winforms而言。但是,如果您想要更强大的解决方案,这里有一个:
    private void RunOnce(ref bool flag, Action callback)
    {
        if (!flag)
        {
            try
            {
                flag = true;
                callback();
            }
            finally
            {
                flag = false;
            }
        }
    }

    private bool inMirror;
    private void realText_TextChanged(object sender, EventArgs e)
    {
        RunOnce(ref inMirror, () =>
        {
            mirrorText.Text = mirror(realText.Text);
        });
    }

    private void mirrorText_TextChanged(object sender, EventArgs e)
    {
        RunOnce(ref inMirror, () =>
        {
            realText.Text = mirror(mirrorText.Text);
        });
    }

    private string mirror(string text)
    {
        return new string(text.Reverse().ToArray()).Replace("\n\r", "\r\n");
    }

P.S. mirror()在代理对上会失败。这里有一些解决方案。


感谢您进行更深入的检查。我可能只会使用简单的TextBox。顺便说一下,我重写了mirror()函数以执行其他操作,那个只是用于测试GUI。 - PurkkaKoodari

0
如果文本框已经有了文本,并且我们尝试用相同的文本更改它,那么 TextChange 事件不会触发,因为新文本与以前的相同。 在您的代码中,realText_TextChanged 事件反转文本并将其更改为 mirrorText。 mirrorText_TextChanged 事件反转文本并尝试更改 realText。 realText 已经有这个文本,所以不会引发 realText_TextChanged 事件。

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