Почему это не вызывает бесконечный цикл событий?
У меня есть простое приложение, которое отменяет любой текст, набранный для него в другом текстовом поле. Уловка, вы можете изменить либо текстовое поле, и изменения будут (буквально) отражены в другом.
Я написал этот код, полагая, что он вызывает проблемы.
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);
}
}
Я, вероятно, сделаю это в любом случае, чтобы быть в безопасности, но нужно ли это проверить это? (Я даже не собираюсь спрашивать, рекомендуется ли это.)
Ответы
Ответ 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.