36

I have a simple application that reverses any text typed to it in another textbox. The catch is, you can modify either textbox and the changes will be (literally) reflected in the other.

I wrote this code, believing for it to cause problems.

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

I then tried it out, believing that it would cause an infinite loop (realText changes mirrorText, another event happens, mirrorText changes realText, etc). However, nothing except the intended behavior happened.

I'm of course happy about this, I could just leave it here. Or could I?

I'm quite sure the TextChanged event is supposed to be fired whenever Text is changed. Is this intended behavior of some error protection in the events, or was I just lucky? Can this code misbehave on another computer, with other build settings, etc? It can be easily fixed:

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

I'll probably do it anyway to be safe, but is it required to check this? (I'm not even going to ask if it's recommended.)

PurkkaKoodari
  • 6,703
  • 6
  • 37
  • 58
  • 3
    Im pretty sure TextChanged event is only fired when the text is changed from the UI not from code. – Magnus Nov 19 '14 at 10:39
  • 5
    @Magnus from MSDN: This event is raised if the Text property is changed by either a programmatic modification or user interaction. – Marton Nov 19 '14 at 10:40
  • @Magnus, you could use Reflector (I think that was what it was called, right? or the JetBrains one... uh... can't remember the name) to check and see! – Daren Thomas Nov 19 '14 at 10:40
  • 11
    It's possible that setting `Text` to exactly the value already has does not trigger the `TextChanged` event, because it doesn't actually change. –  Nov 19 '14 at 10:41
  • @Marton, the in-IDE documentation also says `Event raised when the value of the Text property is changed on Control`. – PurkkaKoodari Nov 19 '14 at 10:41
  • @Marton I stand corrected. – Magnus Nov 19 '14 at 10:43
  • @hvd It seems you were right. Please write an answer, so I can accept it. I made `mirror()` so that it changes input, got a stack overflow. – PurkkaKoodari Nov 19 '14 at 10:45
  • An interesting comment: "Event is only raised if TextBox is in the visual tree" from here http://msdn.microsoft.com/en-us/library/system.windows.controls.textbox.textchanged%28v=vs.95%29.aspx – Marton Nov 19 '14 at 10:45
  • @Pietu1998 I do not know if it is guaranteed, and if it is the case in all implementations (older versions of .NET Framework, and Mono), so I do not know if you should work around it. –  Nov 19 '14 at 10:46
  • @hvd I'm going to assume it's not always the case and add my workaround included in the question in my code. – PurkkaKoodari Nov 19 '14 at 10:48
  • @Marton: That applies to a WPF (and probably Silverlight / WinRT) TextBox. – Willem van Rumpt Nov 19 '14 at 16:13

4 Answers4

31

Per the comments, and as already answered, the TextChanged event is not getting raised when you set the Text property to the value it already has.

It's not clear whether this is something you can safely rely upon. It is a sensible optimisation, and I would be very surprised if future versions of .NET Framework drop it, but I cannot speak for older versions, nor for third-party implementations (Mono).

To be absolutely safe, I would not use the Focused check you put in your question. I would do exactly what the Text setter does now.

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

This has the same advantage of preventing infinite recursion, but plays more nicely with other code you may put in your form that changes the text as a result of some other event.

22

The reason it doesn't cause a loop is that it checks whether the Text property actually changed, i.e. if the new value does not equal the old value. In your case the mirror function happens to reverse itself, which leads to the same text after two passes.

PurkkaKoodari
  • 6,703
  • 6
  • 37
  • 58
Willem van Rumpt
  • 6,490
  • 2
  • 32
  • 44
4

It's pretty easy to check.

First, replace both textbox controls with

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

Second, set the breakpoint on setter. Add these expressions to the Watch window:

  • Name
  • Text
  • value

Third, launch the app, copy '123' from somewhere and paste it to the first textbox. Here it goes:

1st break:

  • Name: "mirrorText"
  • Text: ""
  • value: "321"

2nd break:

  • Name: "realText"
  • Text: "123"
  • value: "123"

3rd... whoops, it does not breaks anymore. To detect why we had to go deeper. Look at referencesource: text box setter does nothing unusual, but TextBoxBase's one looks interesting:

        set {
            if (value != base.Text) { // Gotcha!
                base.Text = value;
                if (IsHandleCreated) {
                    // clear the modified flag
                    SendMessage(NativeMethods.EM_SETMODIFY, 0, 0);
                }
            }
        }

So, as hvd already answered, the reason is the textbox does not raise TextChanged if old and new values are the same. I don't think the behavior will change, at least for winforms. But if you want more robust solution, here it is:

    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() will fail on surrogate pairs. Here're some solutions.

Community
  • 1
  • 1
Sinix
  • 1,306
  • 9
  • 46
  • Thank you for a more in-depth inspection. I'll probably just use the simple TextBox, though. And just by the way, I rewrote mirror() to do something else, that one was just for testing the GUI. – PurkkaKoodari Nov 27 '14 at 10:14
0

If textbox has a Text, and we try to change it with the same Text, the TextChange event is not raising because new text is same as the previous. In your code, the realText_TextChanged event reverses the text and changes the mirrorText with it. The mirrorText_TextChanged event reverses the text and try to change the realText. The realText has already this text and does not raises the realText_TextChanged event.