Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom TextBox with Watermark

0.00/5 (No votes)
25 Jan 2012 12  
An article on how to write your own textbox with the option to use a watermark.

CTextBox_Screenshot.png

Introduction

In this article, I'll show you how to code a custom textbox with a watermark / placeholder. I have been looking all around for a control like this, because I think it is damn sexy to give the user some info when they fill in a formula instead of having labels as hints. It saves space and looks awesome.

Background

I once searched the web for a control like this, but without luck. The only example I got was using some kind of user32.dll call and that was just like the one Windows 7 uses in the Search field of the Start menu. When you click the textbox, the hint goes away and that in my opinion doesn't look very professional. I am not saying mine is pro at all, but it gets the job done in a smooth way. I also found an article where the author used labels to write the watermark, and that was disturbing to use because of its lack of padding. It simply overlaps the blinking cursor which is the signal to the user telling them to type in. So I figured out I would try to use a panel instead because you can set the panel to negative coordinates and that's just what I needed :)

Using the code

It is really simple to use this code. All you have to do is import the DLL to your project and drag and drop it just like you would do with a regular TextBox. Also, in design time, you can set the color of the watermark when it is active or passive. You can also set the text and font of the watermark. By default the font is the same as its parent which will be the textbox base.

So let's start:

Note that I will go through the class chronologically starting from the top. This will cause a lot of errors while writing but in the end, it will work without defects :) Just be patient and create a new class and follow me. Else you can go download the source if you get any errors.

First we declare the class. (Remember to add the following references: System.Windows.Forms and System.Drawing.)

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

namespace ChreneLib.Controls.TextBoxes
{
    public class CTextBox : TextBox
    {

The next thing to do is to declare some variables:

Here we set up a string to hold the watermark text, and set the color of the watermark depending on whether the control has focus or not. We also set up a new panel which will be the place later on to draw the watermark text in. And at last we have a font and a brush. The font will be the font of the drawn watermark, and by default we will set that to its parent's font. The solid brush will be used to hold the color of the font which will change dynamically.

#region Fields

#region Protected Fields

protected string _waterMarkText = "Default Watermark..."; //The watermark text
protected Color _waterMarkColor; //Color of the watermark when the control does not have focus
protected Color _waterMarkActiveColor; //Color of the watermark when the control has focus

#endregion

#region Private Fields

private Panel waterMarkContainer; //Container to hold the watermark
private Font waterMarkFont; //Font of the watermark
private SolidBrush waterMarkBrush; //Brush for the watermark

#endregion

#endregion

Now we will set up our constructor.

The constructor doesn't really do anything other than calling the Initialize method which we will get to in a bit. So go copy paste or whatever you want to :)

#region Constructors

public CTextBox()
{
    Initialize();
}

#endregion

Adding methods

This chapter will be a little long, but I'm sure you will get it all anyways :)

Initialize();

We start out by making our Initialize method which basically just sets our variables to some default values. After that we call the DrawWaterMark() method, which will draw the watermark, so we can see it just as we put it in. Actually I don't think it is needed to be called as we will call it later in our onPaint method, but what the heck. After we have drawn our watermark, we set some event listeners. We need them because they actually decide how the control should look like. For instance, type in some text, and the watermark should be removed, and therefore we will need to write those instructions later on. As you can see, we are adding a new EventHandler, and within the parenthesis we write the methods which should be triggered whenever a specific thing happens, e.g., when we click on the control.

RemoveWaterMark();

Next we will create our method to remove the watermark. This will be called whenever the length of the text the user is typing is greater than zero. So let's say you type something in the textbox, then the control will remove the watermark. And if you decide to delete that text again, the watermark will be redrawn. But for the algorithm, it goes like this. First we check to see if our watermark container (a panel) is created or not. If it isn't created, then we should not do anything, but if it is created, then it should remove it and release the memory allocated for it.

DrawWaterMark();

This method is called whenever the textbox is empty and there isn't already a watermark drawn. First, we initialize the watermark container, and then we set its properties. Then we add a new event listener for when it is clicked. This is used so the control also will get focus if we click the container. If it wasn't there, the control wouldn't get focus and you would have some serious problems gaining focus to that control, and by that I mean, it would be hard to type in some text.

#region Private Methods

/// <summary>
/// Initializes watermark properties and adds CtextBox events
/// </summary>
private void Initialize()
{
    //Sets some default values to the watermark properties
    _waterMarkColor = Color.LightGray;
    _waterMarkActiveColor = Color.Gray;
    waterMarkFont = this.Font;
    waterMarkBrush = new SolidBrush(_waterMarkActiveColor);
    waterMarkContainer = null;

    //Draw the watermark, so we can see it in design time
    DrawWaterMark();

    //Eventhandlers which contains function calls. 
    //Either to draw or to remove the watermark
    this.Enter += new EventHandler(ThisHasFocus);
    this.Leave += new EventHandler(ThisWasLeaved);
    this.TextChanged += new EventHandler(ThisTextChanged);
}

/// <summary>
/// Removes the watermark if it should
/// </summary>
private void RemoveWaterMark()
{
    if (waterMarkContainer != null)
    {
        this.Controls.Remove(waterMarkContainer);
        waterMarkContainer = null;
    }
}

/// <summary>
/// Draws the watermark if the text length is 0
/// </summary>
private void DrawWaterMark()
{
    if (this.waterMarkContainer == null && this.TextLength <= 0)
    {
        waterMarkContainer = new Panel(); // Creates the new panel instance
        waterMarkContainer.Paint += new PaintEventHandler(waterMarkContainer_Paint);
        waterMarkContainer.Invalidate();
        waterMarkContainer.Click += new EventHandler(waterMarkContainer_Click);
        this.Controls.Add(waterMarkContainer); // adds the control
    }
}
#endregion

Adding event listeners

We are almost done. Now we need to add some event listeners.

ThisHasFocus();

This event handler is triggered whenever the control gains focus. First it will change the color of the watermark to the active color. Then we check to see if the user has written anything in the control and if they have, we should not do anything. However if it is empty, we will remove the watermark and then redraw it.

ThisWasLeaved();

So basically, in this event handler, we will check if the user has written anything. If something is written, we will remove the watermark, and again.. I don't know if this is needed, but double check is always better than zero check. Well, and if the user hasn't written anything, we will invalidate the control, which means we will trigger the paint event which will redraw the control. We do that so we make sure the watermark changes its color to the passive color.

ThistextChanged();

This method is triggered whenever the user changes the text. And here we will check for the text length again, so if there is no text in the control, we will draw a watermark, but if there is text, we will remove it.

OnPaint();

We override the existing OnPaint method so we can make sure the watermark will be refreshed whenever the control is invalidated. This will even work in design time.

OnInvalidated();

We also override this method, so we can make sure that the watermark container also gets invalidated.

#region CTextBox Events

private void ThisHasFocus(object sender, EventArgs e)
{
    //if focused use focus color
    waterMarkBrush = new SolidBrush(this._waterMarkActiveColor);

    //The watermark should not be drawn if the user has already written some text
    if (this.TextLength <= 0)
    {
        RemoveWaterMark();
        DrawWaterMark();
    }
}

private void ThisWasLeaved(object sender, EventArgs e)
{
    //if the user has written something and left the control
    if (this.TextLength > 0)
    {
        //Remove the watermark
        RemoveWaterMark();
    }
    else
    {
        //But if the user didn't write anything, Then redraw the control.
        this.Invalidate();
    }
}

private void ThisTextChanged(object sender, EventArgs e)
{
    //If the text of the textbox is not empty
    if (this.TextLength > 0)
    {
        //Remove the watermark
        RemoveWaterMark();
    }
    else
    {
        //But if the text is empty, draw the watermark again.
        DrawWaterMark();
    }
}

#region Overrided Events

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    //Draw the watermark even in design time
    DrawWaterMark();
}

protected override void OnInvalidated(InvalidateEventArgs e)
{
    base.OnInvalidated(e);
    //Check if there is a watermark
    if (waterMarkContainer != null)
        //if there is a watermark it should also be invalidated();
        waterMarkContainer.Invalidate();
}

#endregion

#endregion

Setting up properties

The final step is to set up some properties, and methods so we can change the properties during runtime and during design time. I will not explain this much, because it is just some get/set methods to change the color, font, and text of the watermark. However, you may see something different over each method. These are called attributes, and are used at design time. The category will create a new little node at design time so you can gather the properties in one place. And the description shows some text about what that specific attribute is going to do.

        #region Properties
        [Category("Watermark attribtues")]
        [Description("Sets the text of the watermark")]
        public string WaterMark
        {
            get { return this._waterMarkText; }
            set
            {
                this._waterMarkText = value;
                this.Invalidate();
            }
        }

        [Category("Watermark attribtues")]
        [Description("When the control gaines focus, " + 
             "this color will be used as the watermark's forecolor")]
        public Color WaterMarkActiveForeColor
        {
            get { return this._waterMarkActiveColor; }

            set
            {
                this._waterMarkActiveColor = value;
                this.Invalidate();
            }
        }

        [Category("Watermark attribtues")]
        [Description("When the control looses focus, this color " + 
                     "will be used as the watermark's forecolor")]
        public Color WaterMarkForeColor
        {
            get { return this._waterMarkColor; }

            set
            {
                this._waterMarkColor = value;
                this.Invalidate();
            }
        }

        [Category("Watermark attribtues")]
        [Description("The font used on the watermark. " + 
                     "Default is the same as the control")]
        public Font WaterMarkFont
        {
            get
            {
                return this.waterMarkFont;
            }

            set
            {
                this.waterMarkFont = value;
                this.Invalidate();
            }
        }

        #endregion   
    }
}

Points of interest

This is my very first attempt at writing my own custom control and it has been a very fun experience. I have learned a lot about the .NET Framework and its patterns.

Again thanks for reading :) Let me know if something is wrong.

History

First version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here