2

I'm creating a custom control (a watermarked textbox) and it inherits from Textbox. As of now, the textbox correctly shows the watermark on losing focus when there's no text and deletes it when the textbox gets focus (it even changes the text's color if it's the watermark). What I want it to do is to report that it has no text when it's showing the watermark, so I'm trying to override the Text property.

Code as follows:

public class WatermarkedTextbox : TextBox
{
    private bool _isWatermarked;
    private string _watermark;
    public string Watermark
    {
        get { return _watermark; }
        set { _watermark = value; }
    }


    [Bindable(false), EditorBrowsable(EditorBrowsableState.Never), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public override string Text
    {
        get
        {
            return _isWatermarked ? string.Empty : base.Text;
        }
        set
        {
            base.Text = value;
        }
    }

    public WatermarkedTextbox()
    {
        GotFocus += WatermarkedTextbox_GotFocus;
        LostFocus += WatermarkedTextbox_LostFocus;
    }

    private void WatermarkedTextbox_LostFocus(object sender, EventArgs e)
    {
        if (Text.Length == 0)
        {
            ForeColor = SystemColors.InactiveCaption;
            Text = _watermark;
            _isWatermarked = true;
        }
    }

    private void WatermarkedTextbox_GotFocus(object sender, EventArgs e)
    {
        if (_isWatermarked)
        {
            ForeColor = SystemColors.ControlText;
            Text = string.Empty;
            _isWatermarked = false;
        }
    }
}

Problem is, when the textbox gets focus it does not delete the watermark.

What am I missing/doing wrong here?

Léster
  • 1,177
  • 1
  • 17
  • 39
  • can you just make a new property and set its value in the Text override? – Jacobr365 May 09 '16 at 14:31
  • Possible duplicate of [How do I implement a TextBox that displays "Type here"?](http://stackoverflow.com/questions/2487104/how-do-i-implement-a-textbox-that-displays-type-here) – raidensan May 09 '16 at 14:42
  • 1
    Your overridden Text property getter is also called by Winforms. Used to check if the TextBox needs to be repainted. When it does, the isWatermarked field does not have the correct value yet. Simply move the isWatermarked assignment *before* the Text assignment and it will work. – Hans Passant May 09 '16 at 14:58
  • Hans Passant, please post this as an answer instead of a comment so I can mark it as the solution. Thanks for the explanation. – Léster May 09 '16 at 15:02

4 Answers4

1

Ah sorry I didn't read that clearly. Alternatively you might want to notify not by overriding the Text Property.

You can make use of event as such:

public class WatermarkedTextbox : TextBox, INotifyPropertyChanged
{
    private bool _isWatermarked;
    private string _watermark;
    public string Watermark
    {
        get { return _watermark; }
        set { _watermark = value; }
    }

    public bool IsWaterMarked
    {
        get
        {
            return _isWatermarked;
        }
        set
        {
            _isWatermarked = value;
            OnPropertyChanged("IsWaterMarked");
        }
    }

    public WatermarkedTextbox()
    {
        GotFocus += WatermarkedTextbox_GotFocus;
        LostFocus += WatermarkedTextbox_LostFocus;
    }

    private void WatermarkedTextbox_LostFocus(object sender, EventArgs e)
    {
        if (Text.Length == 0)
        {
            ForeColor = SystemColors.InactiveCaption;
            Text = _watermark;
            IsWaterMarked = true;
        }
    }

    private void WatermarkedTextbox_GotFocus(object sender, EventArgs e)
    {
        if (_isWatermarked)
        {
            ForeColor = SystemColors.ControlText;
            Text = string.Empty;
            IsWaterMarked = false;
        }
    }

    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, e);
    }

    protected void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Then on the Main Form, you can subscribe and add a handler to the PropertyChanged event:

//somewhere, like in the forms constructor, you need to subscribe to this event
watermarkedTextbox2.PropertyChanged += watermarkedTextbox2_PropertyChanged;

// the handler function
void watermarkedTextbox2_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "IsWaterMarked")
    {
        if (watermarkedTextbox2.IsWaterMarked)
            ; //handle here
        else
            ; //handle here
    }
}
1

Windows supports watermarking for text boxes (and other edit controls such as combo boxes), they call it the "cue banner". Note however that this does not work for multi-line text boxes.

Setting the cue banner on a supported control is simply a matter of using the Win32 API to send an EM_SETCUEBANNER message to the control containing the watermark text. Windows will then handle detecting when the control is empty or has focus and do all the hard work for you, you won't need to use events to manage state. The cue banner is also ignored when you get the control's Text property.

I use the following helper class to set the cue banner (works for combo boxes too):

public class CueBannerHelper
{
    #region Win32 API's
    [StructLayout(LayoutKind.Sequential)]
    public struct COMBOBOXINFO
    {
        public int cbSize;
        public RECT rcItem;
        public RECT rcButton;
        public IntPtr stateButton;
        public IntPtr hwndCombo;
        public IntPtr hwndItem;
        public IntPtr hwndList;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }


    /// <summary>Used to get the current Cue Banner on an edit control.</summary>
    public const int EM_GETCUEBANNER = 0x1502;
    /// <summary>Used to set a Cue Banner on an edit control.</summary>
    public const int EM_SETCUEBANNER = 0x1501;


    [DllImport("user32.dll")]
    public static extern bool GetComboBoxInfo(IntPtr hwnd, ref COMBOBOXINFO pcbi);

    [DllImport("user32.dll")]
    public static extern Int32 SendMessage(IntPtr hWnd, int msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
    #endregion

    #region Method members
    public static void SetCueBanner(Control control, string cueBanner) {
        if (control is ComboBox) {
            CueBannerHelper.COMBOBOXINFO info = new CueBannerHelper.COMBOBOXINFO();
            info.cbSize = Marshal.SizeOf(info);

            CueBannerHelper.GetComboBoxInfo(control.Handle, ref info);

            CueBannerHelper.SendMessage(info.hwndItem, CueBannerHelper.EM_SETCUEBANNER, 0, cueBanner);
        }
        else {
            CueBannerHelper.SendMessage(control.Handle, CueBannerHelper.EM_SETCUEBANNER, 0, cueBanner);
        }
    }
    #endregion
}

All that is then required to implement the watermark on the custom TextBox control is the following property (the attributes at the top are for the control's design-time properties) :

/// <summary>
/// Gets or sets the watermark that the control contains.
/// </summary>
[Description("The watermark that the control contains."),
    Category("Appearance"),
    DefaultValue(null),
    Browsable(true)
]
public string Watermark {
    get { return this._watermark; }
    set {
        this._watermark = value;
        CueBannerHelper.SetCueBanner(this, value);
    }
}
Andy Hames
  • 619
  • 1
  • 4
  • 19
0

Delete your overriden Text property, then it will work!

Delete these lines:

[Bindable(false), EditorBrowsable(EditorBrowsableState.Never), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override string Text
{
    get
    {
        return _isWatermarked ? string.Empty : base.Text;
    }
    set
    {
        base.Text = value;
    }
}
  • The purpose of overriding the Text property is to report that the textbox has no text when it's showing the watermark. Not doing so tells everyone checking that the textbox has valid text when, in fact, does not. – Léster May 09 '16 at 14:51
0

Hans Passant's comment was the correct answer to my question. Also, thanks to everyone that took time to offer help.

I finally decided to go the simplest route (handling PropertyChanged seems too convoluted for this particular need and hooking Windows APIs leaves out multiline textboxes so it's not an option).

In case someone needs it, here's the code:

public class WatermarkedTextbox : TextBox
{
    private bool _isWatermarked;
    private string _watermark;
    public string Watermark
    {
        get { return _watermark; }
        set { _watermark = value; }
    }

    [Bindable(false), EditorBrowsable(EditorBrowsableState.Never), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public override string Text
    {
        get
        {
            return _isWatermarked ? string.Empty : base.Text;
        }
        set
        {
            base.Text = value;
        }
    }

    public WatermarkedTextbox()
    {
        GotFocus += WatermarkedTextbox_GotFocus;
        LostFocus += WatermarkedTextbox_LostFocus;
    }

    private void WatermarkedTextbox_LostFocus(object sender, EventArgs e)
    {
        if (Text.Length == 0)
        {
            _isWatermarked = true;
            ForeColor = SystemColors.InactiveCaption;
            Text = _watermark;
        }
    }

    private void WatermarkedTextbox_GotFocus(object sender, EventArgs e)
    {
        if (_isWatermarked)
        {
            _isWatermarked = false;
            ForeColor = SystemColors.ControlText;
            Text = string.Empty;
        }
    }
}
Léster
  • 1,177
  • 1
  • 17
  • 39