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..."; protected Color _waterMarkColor; protected Color _waterMarkActiveColor;
#endregion
#region Private Fields
private Panel waterMarkContainer; private Font waterMarkFont; private SolidBrush waterMarkBrush;
#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
private void Initialize()
{
_waterMarkColor = Color.LightGray;
_waterMarkActiveColor = Color.Gray;
waterMarkFont = this.Font;
waterMarkBrush = new SolidBrush(_waterMarkActiveColor);
waterMarkContainer = null;
DrawWaterMark();
this.Enter += new EventHandler(ThisHasFocus);
this.Leave += new EventHandler(ThisWasLeaved);
this.TextChanged += new EventHandler(ThisTextChanged);
}
private void RemoveWaterMark()
{
if (waterMarkContainer != null)
{
this.Controls.Remove(waterMarkContainer);
waterMarkContainer = null;
}
}
private void DrawWaterMark()
{
if (this.waterMarkContainer == null && this.TextLength <= 0)
{
waterMarkContainer = new Panel(); waterMarkContainer.Paint += new PaintEventHandler(waterMarkContainer_Paint);
waterMarkContainer.Invalidate();
waterMarkContainer.Click += new EventHandler(waterMarkContainer_Click);
this.Controls.Add(waterMarkContainer); }
}
#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)
{
waterMarkBrush = new SolidBrush(this._waterMarkActiveColor);
if (this.TextLength <= 0)
{
RemoveWaterMark();
DrawWaterMark();
}
}
private void ThisWasLeaved(object sender, EventArgs e)
{
if (this.TextLength > 0)
{
RemoveWaterMark();
}
else
{
this.Invalidate();
}
}
private void ThisTextChanged(object sender, EventArgs e)
{
if (this.TextLength > 0)
{
RemoveWaterMark();
}
else
{
DrawWaterMark();
}
}
#region Overrided Events
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawWaterMark();
}
protected override void OnInvalidated(InvalidateEventArgs e)
{
base.OnInvalidated(e);
if (waterMarkContainer != null)
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.