Подтвердить что ты не робот

Почему это не вызывает бесконечный цикл событий?

У меня есть простое приложение, которое отменяет любой текст, набранный для него в другом текстовом поле. Уловка, вы можете изменить либо текстовое поле, и изменения будут (буквально) отражены в другом.

Я написал этот код, полагая, что он вызывает проблемы.

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 changes mirrorText, произойдет другое событие, mirrorText changes realText и т.д.). Однако ничего, кроме предполагаемого поведения, не произошло.

Я, конечно, доволен этим, я мог бы просто оставить его здесь. Или я мог?

Я уверен, что событие TextChanged должно быть запущено при каждом изменении Text. Является ли это предполагаемым поведением некоторой защиты от ошибок в событиях, или мне просто повезло? Может ли этот код ошибочно работать на другом компьютере, с другими настройками сборки и т.д.? Его можно легко зафиксировать:

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

Я, вероятно, сделаю это в любом случае, чтобы быть в безопасности, но нужно ли это проверить это? (Я даже не собираюсь спрашивать, рекомендуется ли это.)

4b9b3361

Ответ 1

В комментариях, и, как уже было сказано, событие TextChanged не возникает, когда вы устанавливаете свойство Text в значение, которое оно уже имеет.

Не ясно, на что можно положиться. Это разумная оптимизация, и я был бы очень удивлен, если будущие версии .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;
}

Это имеет то же преимущество, что предотвращает бесконечную рекурсию, но играет более хорошо с другим кодом, который вы можете поместить в свою форму, который изменяет текст в результате какого-либо другого события.

Ответ 2

Причина, по которой он не вызывает цикл, заключается в том, что он проверяет, действительно ли свойство Text было изменено, т.е. если новое значение не соответствует старому значению. В вашем случае функция mirror имеет место, чтобы отменить себя, что приводит к тому же тексту после двух проходов.

Ответ 3

Это довольно легко проверить.

Сначала замените оба элемента управления текстовыми полями на

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

Во-вторых, установите точку останова на сеттер. Добавьте эти выражения в окно просмотра:

  • Имя
  • Текст
  • значение

В-третьих, запустите приложение, скопируйте "123" откуда-нибудь и вставьте его в первое текстовое поле. Вот оно:

1-й перерыв:

  • Имя: "mirrorText"
  • Текст: ""
  • значение: "321"

2-й перерыв:

  • Имя: "realText"
  • Текст: "123"
  • значение: "123"

Третий... кричит, он больше не ломается. Чтобы понять, почему нам нужно идти глубже. Посмотрите на sourcesource: set box текстового блока ничего не делает необычно, но TextBoxBase one выглядит интересно:

        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() не сработает на суррогатных парах. Вот несколько решений.

Ответ 4

Если текстовое поле имеет текст, и мы пытаемся изменить его с тем же текстом, событие TextChange не поднимается, потому что новый текст такой же, как и предыдущий. В вашем коде событие realText_TextChanged меняет текст и меняет mirrorText. Событие mirrorText_TextChanged меняет текст и пытается изменить realText. RealText уже имеет этот текст и не вызывает событие realText_TextChanged.