none
Strange behaviour when overriding TextBox.Text

    Question

  • I am trying to create a control based on TextBox, which will show the entered text when there is some, and placeholder text when none is entered.

     

    At runtime, my implementation works fine, but not in the designer. It's not a major issue for me, but the behaviour is so strange that I cannot understand what is going on.

     

    The section in question is:

    Code Snippet

     

    public override String Text {

    get { return _ActualText; }

    set {

     

    _ActualText = value;

     

    if (value.Length > 0)

    {

    base.Text = value;

    base.ForeColor = ForeColor;

    }

    else

    {

    base.Text = _PlaceholderText;

    base.ForeColor = _PlaceholderForeColor;

    }

    if (TextChanged != null)

    TextChanged(this, new EventArgs());

     

    }

    }

     

    I place one of these controls on a form in the designer, and set the Text property. If I set it to "", the control displays the placeholder text in the correct colour. If I set the property to a string value, the colour changes as it should, but the text displayed does not change.

     

    If I change the line 'base.Text = value;'  to 'base.Text = "Hello";', this works. I used a messagebox right before the assignment to check the value of 'value', and it is indeed correct.

     

    If I use value.ToUpper(), it is also correct, but (String)value.Clone() does not work. Similarly, value + "dfgdsd" works correctly, but value + "" does not.

     

    I even attempted to create a new string, append a space, then use SubString() to remove the space, then assign that to base.Text. It doesn't work.

     

    In the end, I changed it to:

    Code Snippet

    base.Text = String.Empty;

    base.AppendText(value);

     

    which appears to work in all cases. Note that the control always operated correctly at runtime, it only shows this behaviour in the designer. No amount of cleaning or rebuilding has any effect, either.

     

    Below is the entire class, if any of you are able to reproduce this problem:

    PartTextBox.cs

     

    public partial class PartTextBox : TextBox

    {

    public override String Text {

    get { return _ActualText; }

    set {

    _ActualText = value;

    if (value.Length > 0)

    {

    // No idea why I have to do this, but setting base.Text directly

    // causes many problems in the designer (VS2005)

    base.Clear();

    base.AppendText(value);

    base.ForeColor = ForeColor;

    }

    else

    {

    base.Text = _PlaceholderText;

    base.ForeColor = _PlaceholderForeColor;

    }

    if (TextChanged != null)

    TextChanged(this, new EventArgs());

    }

    }

    private String _ActualText = String.Empty;

    public new event EventHandler TextChanged;

    public String PlaceholderText { get { return _PlaceholderText; } set { _PlaceholderText = value; if (!Focused) base.Text = value; } } private String _PlaceholderText = String.Empty;

    public Color PlaceholderForeColor { get { return _PlaceholderForeColor; } set { _PlaceholderForeColor = value; if (!Focused) base.ForeColor = value; } } private Color _PlaceholderForeColor = SystemColors.GrayText;

    public new Color ForeColor { get { return _ForeColor; } set { _ForeColor = value; if (Focused) base.ForeColor = value; } } private Color _ForeColor = SystemColors.WindowText;

    public PartTextBox()

    {

    InitializeComponent();

    base.ForeColor = _PlaceholderForeColor;

    base.Text = _PlaceholderText;

    base.TextChanged += new EventHandler(PartTextBox_TextChanged);

    }

    void PartTextBox_TextChanged(object sender, EventArgs e)

    {

    if (!Focused || Leaving) return;

    _ActualText = base.Text;

    if(TextChanged != null) TextChanged(this, e);

    }

    protected override void OnEnter(EventArgs e)

    {

    base.Text = _ActualText;

    base.ForeColor = _ForeColor;

    }

    protected override void OnLeave(EventArgs e)

    {

    Leaving = true;

    _ActualText = base.Text;

    if (_ActualText.Length == 0)

    {

    base.Text = _PlaceholderText;

    base.ForeColor = _PlaceholderForeColor;

    }

    Leaving = false;

    } private Boolean Leaving;

    }

     

     

    PartTextBox.Designer.cs

     

    partial class PartTextBox

    {

    ///

    /// Required designer variable.

    ///

    private System.ComponentModel.IContainer components = null;

    ///

    /// Clean up any resources being used.

    ///

    /// true if managed resources should be disposed; otherwise, false.

    protected override void Dispose(bool disposing)

    {

    if (disposing && (components != null))

    {

    components.Dispose();

    }

    base.Dispose(disposing);

    }

    #region Component Designer generated code

    ///

    /// Required method for Designer support - do not modify

    /// the contents of this method with the code editor.

    ///

    private void InitializeComponent()

    {

    components = new System.ComponentModel.Container();

    }

    #endregion

    }

     

    Tuesday, April 24, 2007 5:52 PM

Answers

  • Hi,

     

    The behavior you are seeing is actually caused by the fact that the designer shadows the Text property. I went back, waaaay back, in our source to see if I could find out why we decided that we had to shadow the Text property, but unfortunately I came up empty. The code was added long before I joined this team. So it is not so much a bug as a "by designer design."

     

    Martin

    Monday, April 30, 2007 9:44 PM
  • Hi,

     

    Unfortunately the person who made the change is no longer at the company. For some controls we shadow Text and for some we do not. There are other control designers which shadows various properties for various reasons. So yup, there was a reason for doing it, and removing the code would most likely break some scenario, so unfortunately we are not going to be able to change this behavior.

     

    Sorry,

     

    Martin

    Tuesday, May 01, 2007 5:39 PM

All replies

  • Take it for what it is, but my advice is to not override the .Text property.  Whenever I've tried to, it has resulted in problems, especially when events are associated with the property.  Since you don't have access to the underlying field, you are calling that property again, and probably triggering more events than you're intending to, and if you have listeners for those events using that property in them...  Oh boy! Smile

     

    JU

    Wednesday, April 25, 2007 8:12 PM
  • Thanks for the reply. Turns out it is actually a bug in the designer. You can see it with just a few lines of code:

    Code Snippet


    public class DoNothingLabel : Label
        {
        public new String Text {
            get { return String.Empty; }
            set { /* Do nothing */ } }
        }
    public class DoNothingTextBox : TextBox
        {
        public new String Text {
            get { return String.Empty; }
            set { /* Do nothing */ } }
        }



    The designer will use the 'new' Text on the label, but not on the text box. The compiler gets it right for both (thankfully). Filed a bug report and had it confirmed by Microsoft: Link
    Friday, April 27, 2007 11:52 AM
  • Hi,

     

    The behavior you are seeing is actually caused by the fact that the designer shadows the Text property. I went back, waaaay back, in our source to see if I could find out why we decided that we had to shadow the Text property, but unfortunately I came up empty. The code was added long before I joined this team. So it is not so much a bug as a "by designer design."

     

    Martin

    Monday, April 30, 2007 9:44 PM
  • Martin,

    Thanks for explaining why this occurs. However, does this mean it's not going to be 'fixed'? It may very well be by design, but it's still incorrect and inconsistent with the compiler. Presumably there was a reason why your team chose to do this, but since it causes the designer to behave incorrectly, perhaps another solution could be found? It sounds like the decision was a bit of a workaround, especially considering controls such as label don't have this issue.

    If you can find out (from team members who were around at the time) what the reason was, I'd be most interested.
    Tuesday, May 01, 2007 10:43 AM
  • Hi,

     

    Unfortunately the person who made the change is no longer at the company. For some controls we shadow Text and for some we do not. There are other control designers which shadows various properties for various reasons. So yup, there was a reason for doing it, and removing the code would most likely break some scenario, so unfortunately we are not going to be able to change this behavior.

     

    Sorry,

     

    Martin

    Tuesday, May 01, 2007 5:39 PM
  • Is there any language construct, or part of the framework I can use to work around this? I am not familiar with writing 'designers' for controls, but if you can point me in the right direction I'm sure I could work it out.

    As it is, even opening a form with the control on in a designer means I will have to manually edit the *.designer.cs each and every time to correct the changes the designer wrongly makes. Even if there is some way to tell the designer to leave the control alone (ie. just persist what's already there, and not make any changes), that would be better than nothing.

    I am about to resort to having a UserControl and hosting a TextBox docked to fill inside it with custom methods and properties which just forward to the internal control, which is hugely inconvenient and probably not good for efficiency at runtime either.

    Reimplementing most of TextBox's functionality in a new custom control isn't really an option, especially when the change I am trying to achieve should be a very simple matter. Not to mention that it would be almost impossible to ensure that my own control behaved and looked the same as TextBox in all scenarios.

    Hoping there is some way round this...


    Tuesday, May 01, 2007 7:53 PM
  • I just tried this code, and it seemed to do what you want. Am I missing something?

     

    public class MyTextBox : TextBox

    {

    string myPlaceHolderText = "Place Holder";

    public override string Text

    {

    get

    {

    return base.Text;

    }

    set

    {

    if (value.Length > 0)

    {

    base.Text = value;

    base.ForeColor = Color.Green;

    }

    else

    {

    base.Text = myPlaceHolderText;

    base.ForeColor = Color.Red;

    }

    }

    }

    Tuesday, May 01, 2007 8:50 PM
  • Nope, overriding doesn't do what I need, which is why I was using new. At runtime, not in the designer:

    Create an instance of MyTextBox
    Set the Text property to String.Empty.
    Retrieve the Text property.

    When Text is retrieved, it will equal myPlaceHolderText, and not an empty string as I assigned. Therefore it becomes impossible to assign an empty string to this control. Although I want the control to display a placeholder when it's Text is empty, the placeholder value should never be returned. The designer would also persist this incorrect value.

    Perhaps I could experiment with overriding the Textbox's painting, and just draw the placeholder text manually over the top if Text is empty. Although I remember that some controls exhibit strange behaviour when trying to manipulate their painting, too.

    Wednesday, May 02, 2007 10:46 AM
  • I don't think you can override Paint, but you can probably override the WndProc. When you get a WM_PAINT you set state so that you know what to return in the getter. Call the base wndproc and then clear the state.

     

    I haven't tried it, so I am not 100% that it will work.

     

    Martin

    Wednesday, May 02, 2007 5:28 PM