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

本文关键字:无限 事件 循环 为什么 | 更新日期: 2023-09-27 18:30:55

我有一个简单的应用程序,可以反转在另一个文本框中键入的任何文本。问题是,您可以修改任何一个文本框,更改将(字面上)反映在另一个文本框中。

我写了这段代码,相信它会引起问题。

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,等等)。但是,除了预期的行为之外,什么也没有发生。

我当然对此感到高兴,我可以把它留在这里。或者我可以?

我很确定每当更改Text时,都应该触发TextChanged事件。这是事件中某些错误保护的预期行为,还是我只是运气好?此代码是否可以在另一台计算机上使用其他构建设置等行为异常?它可以很容易地修复:

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

为了安全起见,我可能会这样做,但是需要检查一下吗?(我什至不会问是否推荐。

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

根据注释,并且正如已经回答的那样,当您将 Text 属性设置为它已有的值时,不会引发 TextChanged 事件。

目前尚不清楚这是否是您可以放心依赖的东西。这是一个明智的优化,如果未来版本的.NET Framework放弃它,我会感到非常惊讶,但我不能代表旧版本,也不能代表第三方实现(Mono)。

为了绝对安全,我不会使用您在问题中输入的Focused支票。我会做Text二传手现在所做的。

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

这具有防止无限递归的相同优点,但与您可能放入表单中的其他代码配合得更好,这些代码会因其他事件而更改文本。

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

检查起来很容易。

首先,将两个文本框控件替换为

    class T : TextBox
    {
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                base.Text = value;
            }
        }
    }

其次,在 setter 上设置断点。将这些表达式添加到"监视"窗口:

  • 名字
  • 发短信
  • 价值

第三,启动应用程序,从某个地方复制"123"并将其粘贴到第一个文本框中。事情是这样的:

第一次休息:

  • 名称:"镜像文本"
  • 文本:"
  • 值:"321"

第二次休息:

  • 名称:"真实文本"
  • 文本:"123"
  • 值:"123"

第三... 哎呀,它不再坏了。为了发现为什么我们必须更深入。看看参考来源:文本框设置器没有什么不寻常的,但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");
    }

附言 mirror() 将在代理项对上失败。以下是一些解决方案。

如果文本框有一个文本,并且我们尝试使用相同的文本更改它,则 TextChange 事件不会引发,因为新文本与前一个文本相同。在代码中,realText_TextChanged 事件反转文本并随之更改镜像文本。mirrorText_TextChanged 事件反转文本并尝试更改 realText。realText 已经有此文本,并且不会引发realText_TextChanged事件。