Watermark TextBox in WinForms

.NetWinformsTextboxWatermark

.Net Problem Overview


Can anyone point me to a good implementation of a basic Windows Forms TextBox that will initially show watermark text that disappears when the cursor enters it? I think I can create my own with some creative use of Enter and Leave events, but I'm sure there's a perfectly usable implementation sitting around somewhere. I saw the WPF implementation and if necessary I could nest it, but a native WinForms TextBox derivative would be better.

I have this so far; haven't tried it yet but does anyone see any glaring problems?

public class WatermarkTextBox:TextBox
{
    public string WatermarkText { get; set; }

    public Color WatermarkColor { get; set; }

    private Color TextColor { get; set; }

    private bool isInTransition;

    public WatermarkTextBox()
    {
        WatermarkColor = SystemColors.GrayText;
    }

    private bool HasText { get { return Text.IsNotNullOrBlankOr(WatermarkText); }}

    protected override void OnEnter(EventArgs e)
    {
        base.OnEnter(e);

        if (HasText) return;

        isInTransition = true;
        ForeColor = TextColor;
        Text = String.Empty;
        isInTransition = false;
    }

    protected override void OnForeColorChanged(EventArgs e)
    {
        base.OnForeColorChanged(e);
        if (!isInTransition) //the change came from outside
            TextColor = ForeColor;
    }

    protected override void OnLeave(EventArgs e)
    {
        base.OnLeave(e);

        if (HasText) return;

        isInTransition = true;
        ForeColor = WatermarkColor;
        Text = WatermarkText.EmptyIfNull();
        isInTransition = false;
    }
}

EDIT: The above would have eventually worked with some finessing, but the CueProvider worked much better. Here's my final implementation:

public class WatermarkTextBox:TextBox
{
    private string watermarkText;
    public string WatermarkText
    {
        get { return watermarkText; }
        set
        {
            watermarkText = value;
            if (watermarkText.IsNullOrBlank())
                CueProvider.ClearCue(this);
            else
                CueProvider.SetCue(this, watermarkText);
        }
    }
}

I could have integrated the CueProvider functionality completely, but this works beautifully.

.Net Solutions


Solution 1 - .Net

The official term is "cue banner". Here's another way to do it, just inheriting TextBox gets the job done too. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox and set the Cue property.

You get a live preview of the Cue value in the designer, localized to the form's Language property. Lots of bang for very little buck, an excellent demonstration of the good parts of Winforms.

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class CueTextBox : TextBox {
    [Localizable(true)]
    public string Cue {
        get { return mCue; }
        set { mCue = value; updateCue(); }
    }

    private void updateCue() {
        if (this.IsHandleCreated && mCue != null) {
            SendMessage(this.Handle, 0x1501, (IntPtr)1, mCue);
        }
    }
    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        updateCue();
    }
    private string mCue;

    // PInvoke
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, string lp);
}

Solution 2 - .Net

I've updated the answer given by @Hans Passant above to introduce constants, make it consistent with pinvoke.net definitions and to let the code pass FxCop validation.

class CueTextBox : TextBox
{
    private static class NativeMethods
    {
        private const uint ECM_FIRST = 0x1500;
        internal const uint EM_SETCUEBANNER = ECM_FIRST + 1;
	
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, string lParam);
    }

    private string _cue;

    public string Cue
    {
        get
        {
            return _cue;
        }
        set
        {
            _cue = value;
            UpdateCue();
        }
    }

    private void UpdateCue()
    {
        if (IsHandleCreated && _cue != null)
        {
            NativeMethods.SendMessage(Handle, NativeMethods.EM_SETCUEBANNER, (IntPtr)1, _cue);
        }
    }

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        UpdateCue();
    }
}

Edit: update the PInvoke call to set CharSet attribute, to err on the safe side. For more info see the SendMessage page at pinvoke.net.

Solution 3 - .Net

.NET 5.0+, .NET Core 3.0+

You can use PlaceholderText property. It supports both multi-line and single-line text-box.

textBox1.PlaceholderText = "Something";

If you look into the .NET CORE implementation of TextBox you see it's been implemented by handling paint message.

.NET Framework

Here is an implementation of a TextBox which supports showing hint (or watermark or cue):

  • It also shows the hint when MultiLine is true.
  • It's based on handling WM_PAINT message and drawing the hint. So you can simply customize the hint and add some properties like hint color, or you can draw it right to left or control when to show the hint.

using System.Drawing;
using System.Windows.Forms;
public class ExTextBox : TextBox
{
    string hint;
    public string Hint
    {
        get { return hint; }
        set { hint = value; this.Invalidate(); }
    }
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == 0xf)
        {
            if (!this.Focused && string.IsNullOrEmpty(this.Text)
                && !string.IsNullOrEmpty(this.Hint))
            {
                using (var g = this.CreateGraphics())
                {
                    TextRenderer.DrawText(g, this.Hint, this.Font,
                        this.ClientRectangle, SystemColors.GrayText , this.BackColor, 
                        TextFormatFlags.Top | TextFormatFlags.Left);
                }
            }
        }
    }
}

If you use EM_SETCUEBANNER, then there will be 2 issues. The text always will be shown in a system default color. Also the text will not be shown when the TextBox is MultiLine.

Using the painting solution, you can show the text with any color that you want. You also can show the watermark when the control is multi-line:

enter image description here

Download

You can clone or download the working example:

Solution 4 - .Net

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);

And the message constants:

private const uint EM_SETCUEBANNER = 0x1501;
private const uint CB_SETCUEBANNER = 0x1703;    // minimum supported client Windows Vista, minimum supported server Windows Server 2008

And imho the best way to implement it is as an extension method.
So for the TextBox control the syntax would be:

MyTextBox.CueBanner(false, "Password");

From the code:

public static void CueBanner(this TextBox textbox, bool showcuewhenfocus, string cuetext)
{
	uint BOOL = 0;
	if (showcuewhenfocus == true) { BOOL = 1; }

	SendMessage(textbox.Handle, EM_SETCUEBANNER, (IntPtr)BOOL, cuetext); ;
}

Solution 5 - .Net

Using WinForms on .NET Core:

This has been greatly simplified in .NET Core. You can directly add placeholder text by modifying the new PlaceholderText Property of the TextBox.

public virtual string PlaceholderText { get; set; }

Properties

Placeholder text in WinForms .NET Core Application

Note that you would probably still have to edit the ForeColor if you want to get a colored Placeholder Text. The PlaceholderText field is visible when the Text field is null or empty.

Solution 6 - .Net

You can add a watermark to a Textbox (multiline or not) that works pretty well by drawing it in different controls Paint event. For Example:

    Private Sub Panel1_Paint(sender As Object, e As PaintEventArgs) Handles Panel1.Paint
        If TextBox1.Text = "" Then
            TextBox1.CreateGraphics.DrawString("Enter Text Here", Me.Font, New SolidBrush(Color.LightGray), 0, 0)
        End If
    End Sub

-OO-

Solution 7 - .Net

I wrote a reusable custom control class for my project.
maybe it can help someone that need to implement multiple placeholder textboxes in his project. here is the C# and vb.net versions:

C#:

namespace reusebleplaceholdertextbox
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // implementation
            CustomPlaceHolderTextbox myCustomTxt = new CustomPlaceHolderTextbox(
                "Please Write Text Here...", Color.Gray, new Font("ARIAL", 11, FontStyle.Italic)
                , Color.Black, new Font("ARIAL", 11, FontStyle.Regular)
                );

            myCustomTxt.Multiline = true;
            myCustomTxt.Size = new Size(200, 50);
            myCustomTxt.Location = new Point(10, 10);
            this.Controls.Add(myCustomTxt);
        }
    }

    class CustomPlaceHolderTextbox : System.Windows.Forms.TextBox
    {
        public string PlaceholderText { get; private set; }
        public Color PlaceholderForeColor { get; private set; }
        public Font PlaceholderFont { get; private set; }

        public Color TextForeColor { get; private set; }
        public Font TextFont { get; private set; }

        public CustomPlaceHolderTextbox(string placeholdertext, Color placeholderforecolor,
            Font placeholderfont, Color textforecolor, Font textfont)
        {
            this.PlaceholderText = placeholdertext;
            this.PlaceholderFont = placeholderfont;
            this.PlaceholderForeColor = placeholderforecolor;
            this.PlaceholderFont = placeholderfont;
            this.TextForeColor = textforecolor;
            this.TextFont = textfont;
            if (!string.IsNullOrEmpty(this.PlaceholderText))
            {
                SetPlaceHolder(true);
                this.Update();
            }
        }

        private void SetPlaceHolder(bool addEvents)
        {
            if (addEvents)
            {  
                this.LostFocus += txt_lostfocus;
                this.Click += txt_click;
            }

            this.Text = PlaceholderText;
            this.ForeColor = PlaceholderForeColor;
            this.Font = PlaceholderFont;
        }

        private void txt_click(object sender, EventArgs e)
        {
            // IsNotFirstClickOnThis:
            // if there is no other control in the form
            // we will have a problem after the first load
            // because we dont other focusable control to move the focus to
            // and we dont want to remove the place holder
            // only on first time the place holder will be removed by click event
            RemovePlaceHolder();
            this.GotFocus += txt_focus;
            // no need for this event listener now
            this.Click -= txt_click;
        }

        private void RemovePlaceHolder()
        {
            this.Text = "";
            this.ForeColor = TextForeColor;
            this.Font = TextFont;
        }
        private void txt_lostfocus(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(this.Text))
            {
                // set placeholder again
                SetPlaceHolder(false);
            }
        }

        private void txt_focus(object sender, EventArgs e)
        {
            if (this.Text == PlaceholderText)
            {
                // IsNotFirstClickOnThis:
                // if there is no other control in the form
                // we will have a problem after the first load
                // because we dont other focusable control to move the focus to
                // and we dont want to remove the place holder
                RemovePlaceHolder();
            }
        }
    }
}


VB.NET:


Namespace CustomControls

    Public Class PlaceHolderTextBox
        Inherits System.Windows.Forms.TextBox

        Public Property PlaceholderText As String
        Public Property PlaceholderForeColor As Color
        Public Property PlaceholderFont As Font
        Public Property TextForeColor As Color
        Public Property TextFont As Font

        Public Sub New(ByVal placeholdertext As String, ByVal placeholderforecolor As Color, ByVal placeholderfont As Font, ByVal txtboxbackcolor As Color, ByVal textforecolor As Color, ByVal textfont As Font)
            Me.PlaceholderText = placeholdertext
            Me.PlaceholderFont = placeholderfont
            Me.PlaceholderForeColor = placeholderforecolor
            Me.PlaceholderFont = placeholderfont
            Me.TextForeColor = textforecolor
            Me.TextFont = textfont
            Me.BackColor = txtboxbackcolor
            If Not String.IsNullOrEmpty(Me.PlaceholderText) Then
                SetPlaceHolder(True)
                Me.Update()
            End If
        End Sub

        Private Sub SetPlaceHolder(ByVal addEvents As Boolean)
            If addEvents Then
                AddHandler Me.LostFocus, AddressOf txt_lostfocus
                AddHandler Me.Click, AddressOf txt_click
            End If

            Me.Text = PlaceholderText
            Me.ForeColor = PlaceholderForeColor
            Me.Font = PlaceholderFont
        End Sub

        Private Sub txt_click(ByVal sender As Object, ByVal e As EventArgs)
            RemovePlaceHolder()
            AddHandler Me.GotFocus, AddressOf txt_focus
            RemoveHandler Me.Click, AddressOf txt_click
        End Sub

        Private Sub RemovePlaceHolder()
            Me.Text = ""
            Me.ForeColor = TextForeColor
            Me.Font = TextFont
        End Sub

        Private Sub txt_lostfocus(ByVal sender As Object, ByVal e As EventArgs)
            If String.IsNullOrEmpty(Me.Text) Then
                SetPlaceHolder(False)
            End If
        End Sub

        Private Sub txt_focus(ByVal sender As Object, ByVal e As EventArgs)
            If Me.Text = PlaceholderText Then
                RemovePlaceHolder()
            End If
        End Sub
    End Class

End Namespace

Solution 8 - .Net

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace PlaceHolderTextBoxCSharp
{
    public class CTextBox : TextBox
    {
        private Panel contenedor;
        protected string texto = "PlaceHolderText";
        protected Color colorTextoDefault = Color.Gray;
        public Color colorTexto = Color.Gray;
        protected Color colorTextoObligatorio = Color.Red;
        private Font fuente;
        private SolidBrush establecerColorTexto;
        private bool obligatoriedad = false;
        private bool colorConFoco = false;
        private int vuelta = 0;

        public CTextBox()
        {
            Inicializar();
        }

        private void Inicializar()
        {
            fuente = Font;
            CharacterCasing = CharacterCasing.Upper;
            contenedor = null;

            MuestraPlaceHolder();

            Leave += new EventHandler(PierdeFoco);
            TextChanged += new EventHandler(CambiaTexto);
        }

        private void EliminaPlaceHolder()
        {
            if (contenedor != null)
            {
                Controls.Remove(contenedor);
                contenedor = null;
            }
        }

        private void MuestraPlaceHolder()
        {
            if (contenedor == null && TextLength <= 0)
            {
                contenedor = new Panel();
                contenedor.Paint += new PaintEventHandler(contenedorPaint);
                contenedor.Invalidate();
                contenedor.Click += new EventHandler(contenedorClick);
                Controls.Add(contenedor);
            }
        }

        private void contenedorClick(object sender, EventArgs e)
        {
            Focus();
        }

        private void contenedorPaint(object sender, PaintEventArgs e)
        {
            contenedor.Location = new Point(2, 0);
            contenedor.Height = Height;
            contenedor.Width = Width;
            contenedor.Anchor = AnchorStyles.Left | AnchorStyles.Right;
            establecerColorTexto = new SolidBrush(colorTexto);
            Graphics g = e.Graphics;
            g.DrawString(texto, fuente, establecerColorTexto, new PointF(-1f, 1f));
        }

        private void PierdeFoco(object sender, EventArgs e)
        {
            if (TextLength > 0)
            {
                EliminaPlaceHolder();
            }
            else
            {
                if (obligatoriedad == true)
                {
                    colorTexto = colorTextoObligatorio;
                }
                else
                {
                    colorTexto = colorTextoDefault;
                }

                Invalidate();
            }
        }

        private void CambiaTexto(object sender, EventArgs e)
        {
            if (TextLength > 0)
            {
                EliminaPlaceHolder();
            }
            else
            {
                MuestraPlaceHolder();

                vuelta += 1;

                if (vuelta >= 1 && obligatoriedad == true)
                {
                    colorTexto = colorTextoObligatorio;
                }
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            MuestraPlaceHolder();
        }

        protected override void OnInvalidated(InvalidateEventArgs e)
        {
            base.OnInvalidated(e);

            if (contenedor != null)
            {
                contenedor.Invalidate();
            }
        }

        [Category("Atributos PlaceHolder")]
        [Description("Establece el texto a mostrar.")]

        public string PlaceHolderText
        {
            get
            {
                return texto;
            }
            set
            {
                texto = value;
                Invalidate();
            }
        }

        [Category("Atributos PlaceHolder")]
        [Description("Establece el estilo de fuente del PlaceHolder.")]

        public Font PlaceHolderFont
        {
            get
            {
                return fuente;
            }
            set
            {
                fuente = value;
                Invalidate();
            }
        }

        [Category("Atributos PlaceHolder")]
        [Description("Indica si el campo es obligatorio.")]

        public bool PlaceHolderFieldRequired
        {
            get
            {
                return obligatoriedad;
            }
            set
            {
                obligatoriedad = value;
                Invalidate();
            }
        }
    }
}

Solution 9 - .Net

With .Net Core 3 a property was introduced into TextBox: PlaceHolderText

If one is going to need this in a FrameWork application the required code parts can be taken from the official open source code and placed in a TextBox descendant (see license).

This supports multiline TextBox and also RTL text.

public class PlaceHolderTextBox : TextBox
{
    private const int WM_KILLFOCUS = 0x0008;
    private const int WM_PAINT = 0x000F;

    private string _placeholderText;

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        if (this.ShouldRenderPlaceHolderText(m))
        {
            using Graphics g = this.CreateGraphics();
            this.DrawPlaceholderText(g);
        }
    }

    #region PlaceHolder

    /// <summary>
    ///  Gets or sets the text that is displayed when the control has no text and does not have the focus.
    /// </summary>
    /// <value>The text that is displayed when the control has no text and does not have the focus.</value>
    [Localizable(true), DefaultValue("")]
    public virtual string PlaceholderText
    {
        get => _placeholderText;
        set
        {
            if (value == null)
            {
                value = string.Empty;
            }

            if (_placeholderText != value)
            {
                _placeholderText = value;
                if (this.IsHandleCreated)
                {
                    this.Invalidate();
                }
            }
        }
    }

    //-------------------------------------------------------------------------------------------------

    /// <summary>
    ///  Draws the <see cref="PlaceholderText"/> in the client area of the <see cref="TextBox"/> using the default font and color.
    /// </summary>
    private void DrawPlaceholderText(Graphics graphics)
    {
        TextFormatFlags flags = TextFormatFlags.NoPadding | TextFormatFlags.Top |
                                TextFormatFlags.EndEllipsis;
        Rectangle rectangle = this.ClientRectangle;

        if (this.RightToLeft == RightToLeft.Yes)
        {
            flags |= TextFormatFlags.RightToLeft;
            switch (this.TextAlign)
            {
                case HorizontalAlignment.Center:
                    flags |= TextFormatFlags.HorizontalCenter;
                    rectangle.Offset(0, 1);
                    break;
                case HorizontalAlignment.Left:
                    flags |= TextFormatFlags.Right;
                    rectangle.Offset(1, 1);
                    break;
                case HorizontalAlignment.Right:
                    flags |= TextFormatFlags.Left;
                    rectangle.Offset(0, 1);
                    break;
            }
        }
        else
        {
            flags &= ~TextFormatFlags.RightToLeft;
            switch (this.TextAlign)
            {
                case HorizontalAlignment.Center:
                    flags |= TextFormatFlags.HorizontalCenter;
                    rectangle.Offset(0, 1);
                    break;
                case HorizontalAlignment.Left:
                    flags |= TextFormatFlags.Left;
                    rectangle.Offset(1, 1);
                    break;
                case HorizontalAlignment.Right:
                    flags |= TextFormatFlags.Right;
                    rectangle.Offset(0, 1);
                    break;
            }
        }

        TextRenderer.DrawText(graphics, this.PlaceholderText, this.Font, rectangle, SystemColors.GrayText, this.BackColor, flags);
    }
    
    private bool ShouldRenderPlaceHolderText(in Message m) =>
        !string.IsNullOrEmpty(this.PlaceholderText) &&
        (m.Msg == WM_PAINT || m.Msg == WM_KILLFOCUS) &&
        !this.GetStyle(ControlStyles.UserPaint) &&
        !this.Focused && this.TextLength == 0;

    #endregion
}

Solution 10 - .Net

Private Sub randomSubName() Handles txtWatermark.Click
   txtWatermark.text = ""
End Sub

Make the default text of the textbox whatever you want the watermark to be, I assume in this example you name the textbox txtWatermark

Hey, I'm new. So sorry if I terribly screwed up the post... I also have no idea if this works...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKeithSView Question on Stackoverflow
Solution 1 - .NetHans PassantView Answer on Stackoverflow
Solution 2 - .Netg tView Answer on Stackoverflow
Solution 3 - .NetReza AghaeiView Answer on Stackoverflow
Solution 4 - .NetdeegeeView Answer on Stackoverflow
Solution 5 - .NetVirtualValentinView Answer on Stackoverflow
Solution 6 - .NetPKanoldView Answer on Stackoverflow
Solution 7 - .NetJonathan ApplebaumView Answer on Stackoverflow
Solution 8 - .NetHoracio QuinteroView Answer on Stackoverflow
Solution 9 - .NettobsterView Answer on Stackoverflow
Solution 10 - .NetredstonepizzaView Answer on Stackoverflow