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

WaterMark TextBox For Desktop Applications Using C#, .NET 3.5 and VS2008

0.00/5 (No votes)
22 Aug 2008 1  
Watermark TextBox for .NET
WaterMarkTextBox.png

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();/*thanks to Bernhard Elbl
                                                              for Invalidate()*/ }
            }

            private string _waterMarkText = "Water Mark";
            public string WaterMarkText
            {
                get { return _waterMarkText; }
                set { _waterMarkText = value; Invalidate(); }
            }
        #endregion

        //Default constructor
        public WaterMarkTextBox()
        {
            JoinEvents(true);
        }

        //Override OnCreateControl ... thanks to  "lpgray .. codeproject guy"
        protected override void OnCreateControl() 
        { 
            base.OnCreateControl();
            WaterMark_Toggel(null, null); 
        }
        
        //Override OnPaint
        protected override void OnPaint(PaintEventArgs args)
        {
            // Use the same font that was defined in base class
            System.Drawing.Font drawFont = new System.Drawing.Font(Font.FontFamily,
                Font.Size, Font.Style, Font.Unit);
            //Create new brush with gray color or 
            SolidBrush drawBrush = new SolidBrush(WaterMarkColor);//use Water mark color
            //Draw Text or WaterMark
            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);
                //No one of the above events will start immeddiatlly 
                //TextBox control still in constructing, so,
                //Font object (for example) couldn't be catched from within
                //WaterMark_Toggle
                //So, call WaterMark_Toggel through OnCreateControl after TextBox
                //is totally created
                //No doupt, it will be only one time call
                
                //Old solution uses Timer.Tick event to check Create property
            }
        }

        private void WaterMark_Toggel(object sender, EventArgs args )
        {
            if (this.Text.Length <= 0)
                EnableWaterMark();
            else
                DisbaleWaterMark();
        }

        private void EnableWaterMark()
        {
            //Save current font until returning the UserPaint style to false (NOTE:
            //It is a try and error advice)
            oldFont = new System.Drawing.Font(Font.FontFamily, Font.Size, Font.Style,
               Font.Unit);
            //Enable OnPaint event handler
            this.SetStyle(ControlStyles.UserPaint, true);
            this.waterMarkTextEnabled = true;
            //Triger OnPaint immediatly
            Refresh();
        }

        private void DisbaleWaterMark()
        {
            //Disbale OnPaint event handler
            this.waterMarkTextEnabled = false;
            this.SetStyle(ControlStyles.UserPaint, false);
            //Return back oldFont if existed
            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.

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