Introduction
During my work in a (CMS) Content Management System, and in the module of publishing (which is a .NET desktop application) I needed a watermark feature to help users to know the functionality of a certain TextBox.
WaterMark is a gray text in a TextBox that must disappears once the user starts to input his text, and must again appear if the user erases all of his text characters.
Background
I discovered that TextBox doesn't support this nice feature (am I wrong!?) Whatever, I enjoyed sub classing TextBox to implement a watermark feature myself, it reminded me of the past/nice days of VC++.
It didn't take more than 8 hours to subclass the TextBox control (.NET 3.5) and then obtain a good feature like watermark. I Googled three web-pages or more in order to know how to enable and disable the overriding of the OnPaint
handler, where, OnPaint
is ignored by default until modifying the control style UserPaint
, which can be modified by using the following statement:
this.SetStyle(ControlStyles.UserPaint, true);
And to render WaterMarkText on the client area of the TextBox control it was easy to use PaintEventArgs.DrawString(...)
, because it works just as easily as setting the TextBox.Text
property. Take a look:
args.Graphics.DrawString((WaterMarkTextEnabled ? WaterMarkText : Text),
drawFont, drawBrush, new PointF(0.0F, 0.0F));
I was also in need of subscribing some events to toggle WaterMark On and Off according to the state of the TextBox.Text
property. It may be empty (which means WaterMark-enabled) or full of characters (means WaterMark-disabled). This includes events like:
this.TextChanged +=
new System.EventHandler(this.WaterMark_Toggel);
this.LostFocus
+= new System.EventHandler(this.WaterMark_Toggel);
Careful — while
TextBox is in the state of "WaterMark-enabled", the
TextBox.Font
property seems to be garbage collected after calling
this.SetStyle(...)
. This seems to occur when using the
Brush
property for example, (you find this by trial and error), so, I was obliged to save
TextBox.Font
into a
oldFont
member variable until WaterMark goes to a "disable" state, then returns back to its origin.
TextBox.Font
may itself be changed for various reasons (i.e., a user may decide to change the font), so, I was again obliged to subscribe the TextBox.FontChanged
event to update oldFont
so that at the correct time of modification, subscription is done by using the following statement:
this.FontChanged
+= new System.EventHandler(this.WaterMark_FontChanged);
At that time, the joined events will not be capable to change the state of TextBox-control/WaterMark-feature, so, the constructor must initialize the WaterMark-toggling process along with joining these events at the time of construction. But, the control isn't yet constructed/created totally, where some members like TextBox.Font
are not created, and we will not be able to catch oldFont
. So, we must stay waiting for the control for awhile until it is created totally and then call WaterMark_Toggel
. To solve that problem I used OnCreateControl handler that makes it possible to trigger WaterMark_Toggel at the end of the creation process (thanks is to 'lpgray' who tolled me about OnCreateControl).
Again, WaterMark toggling is being done by calling WaterMark_Toggel(null,null)
which depends on TextBox.Text to decide which state should be assigned to the control (WaterMark-enable or WaterMark-disable). Take a look at the JoinEvents(true)
member function.
Using the Code
This code was written on Visual Studio 2008, .NET 3.5 and C#.
To use the code just take a copy from the file WaterMarkTextBox.cs or from the following code snippet, and place it in your project, then, build the project, you will get a component icon on your ToolBox named WaterMarkTextBox. Drag the icon to your form and enjoy WaterMarkText and WaterMarkColor properties.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace wmgCMS
{
class WaterMarkTextBox : TextBox
{
private Font oldFont = null;
private Boolean waterMarkTextEnabled = false;
#region Attributes
private Color _waterMarkColor = Color.Gray;
public Color WaterMarkColor
{
get { return _waterMarkColor; }
set { _waterMarkColor = value; Invalidate(); }
}
private string _waterMarkText = "Water Mark";
public string WaterMarkText
{
get { return _waterMarkText; }
set { _waterMarkText = value; Invalidate(); }
}
#endregion
public WaterMarkTextBox()
{
JoinEvents(true);
}
protected override void OnCreateControl()
{
base.OnCreateControl();
WaterMark_Toggel(null, null);
}
protected override void OnPaint(PaintEventArgs args)
{
System.Drawing.Font drawFont = new System.Drawing.Font(Font.FontFamily,
Font.Size, Font.Style, Font.Unit);
SolidBrush drawBrush = new SolidBrush(WaterMarkColor);
args.Graphics.DrawString((waterMarkTextEnabled ? WaterMarkText : Text),
drawFont, drawBrush, new PointF(0.0F, 0.0F));
base.OnPaint(args);
}
private void JoinEvents(Boolean join)
{
if (join)
{
this.TextChanged += new System.EventHandler(this.WaterMark_Toggel);
this.LostFocus += new System.EventHandler(this.WaterMark_Toggel);
this.FontChanged += new System.EventHandler(this.WaterMark_FontChanged);
}
}
private void WaterMark_Toggel(object sender, EventArgs args )
{
if (this.Text.Length <= 0)
EnableWaterMark();
else
DisbaleWaterMark();
}
private void EnableWaterMark()
{
oldFont = new System.Drawing.Font(Font.FontFamily, Font.Size, Font.Style,
Font.Unit);
this.SetStyle(ControlStyles.UserPaint, true);
this.waterMarkTextEnabled = true;
Refresh();
}
private void DisbaleWaterMark()
{
this.waterMarkTextEnabled = false;
this.SetStyle(ControlStyles.UserPaint, false);
if(oldFont != null)
this.Font = new System.Drawing.Font(oldFont.FontFamily, oldFont.Size,
oldFont.Style, oldFont.Unit);
}
private void WaterMark_FontChanged(object sender, EventArgs args)
{
if (waterMarkTextEnabled)
{
oldFont = new System.Drawing.Font(Font.FontFamily,Font.Size,Font.Style,
Font.Unit);
Refresh();
}
}
}
}
Points of Interest
Subclassing .NET controls.
History
- 15/6/2008: Posted version 1.0.
- 9/8/2008: Posted version 1.5. - Use 'OnCreateCotrol' instead of ' Timer.Tick', use 'Invalidate' instead of 'Refresh' and remove unwanted property 'WaterMarkTextEnabled'
- 22/8/2008: Posted version 1.6. - Update demo application to show how to use WatermarkColor and Font properly.