Jaa


DataGridView Watermark Cell

I returned from my end-of-the-year vacation to find the following reader comment in my inbox:
> I am trying to create a custom DGV column that will show a watermark for the empty cells , in the same manner like windows live search box ... any advice?

In case you are not familiar with this behavior, a watermark is typically a bit of help text that appears in a text box when it is "empty". For example, the search box in the upper-right corner of IE7 says "Live Search" by default, and the MSDN Library search box says "Search MSDN with Live Search". Typically, the text is in italics or is grey to differentiate it from an ordinary textbox value. As soon as you click in the text box, the help text disappears so that you can type your search terms without distraction.

This seemed like an interesting challenge, so I implemented the DataGridViewWatermarkCell and DataGridViewWatermarkColumn classes shown at the end of this post. All the action is in the cell's Paint method. The main bit of code just sets the formattedValue parameter in the Paint method to the watermark text and tweaks the cell styles.

Of course, the watermark code is only used at run time when there is no cell value, the watermark text has been set, and the cell is not in edit mode. The first part of the if statement in the cell Paint method prevents painting issues in the designer:

if ((OwningColumn.Site == null || !OwningColumn.Site.DesignMode) && ...

It appears that the OwningColumn only has a Site in design mode, but I included the DesignMode check to make the code more readable. The rest of the if statement checks for edit mode (but only if the cell's row is not shared - in which case RowIndex == -1), checks whether the watermark text has been set, and checks whether there is a cell value (using GetValue in case the row is shared).

There are a few other tasks needed to make the code usable in the Visual Studio designer. The column class, for example, is simply a column specialized to use watermark cells by default, and enables you to use the designer to add a watermark column and set the WatermarkText property. Like all specialized DataGridView column types, setting the property at the column level propogates the value to the cell template and to all existing cells in that column. You can override the column value for individual cells by setting the cell WatermarkText property only after you set the column property.

Another important designer consideration is the cell's Clone method override. You must override Clone to copy the WatermarkText value. If you leave out the Clone method, you can change the column WatermarkText value in the designer, but the value won't stick. Note that the column does not have to override Clone because the column always gets its WatermarkText value from the template cell. The cell's Clone method does the work for both the cell and the column.

So there you have it! Clearly, this is a primitive implementation, but it should give you the basic idea if you want to take it further. For example, you might want to make more of the watermark properties (such as styles) available to the client programmer, or allow an Image watermark.

Enjoy!

using System;

using System.ComponentModel;

using System.Drawing;

using System.Windows.Forms;

namespace DataGridViewWatermark

{

    public class DataGridViewWatermarkColumn : DataGridViewTextBoxColumn

    {

        public DataGridViewWatermarkColumn()

        {

            CellTemplate = new DataGridViewWatermarkCell();

        }

        public String WatermarkText

        {

            get

            {

                if (((DataGridViewWatermarkCell)CellTemplate) == null)

                {

          throw new InvalidOperationException("cell template required");

                }

                return ((DataGridViewWatermarkCell)CellTemplate).WatermarkText;

            }

            set

            {

                if (this.WatermarkText != value)

                {

                    ((DataGridViewWatermarkCell)CellTemplate).WatermarkText = value;

                    if (this.DataGridView != null)

                    {

                        DataGridViewRowCollection dataGridViewRows =

                            this.DataGridView.Rows;

                        int rowCount = dataGridViewRows.Count;

                        for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)

                        {

                            DataGridViewRow dataGridViewRow =

                                dataGridViewRows.SharedRow(rowIndex);

                            DataGridViewWatermarkCell cell =

                                dataGridViewRow.Cells[this.Index]

                                as DataGridViewWatermarkCell;

                            if (cell != null)

                            {

                                cell.WatermarkText = value;

                            }

                        }

                    }

            }

            }

        }

    }

    public class DataGridViewWatermarkCell : DataGridViewTextBoxCell

    {

        private String watermarkTextValue;

        public String WatermarkText

        {

            get { return watermarkTextValue; }

            set { watermarkTextValue = value; }

        }

        public override object Clone()

        {

            DataGridViewWatermarkCell cell = (DataGridViewWatermarkCell)base.Clone();

            cell.WatermarkText = this.WatermarkText;

         return cell;

        }

        protected override void Paint(Graphics graphics, Rectangle clipBounds,

            Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState,

            object value, object formattedValue, string errorText,

            DataGridViewCellStyle cellStyle,

            DataGridViewAdvancedBorderStyle advancedBorderStyle,

            DataGridViewPaintParts paintParts)

        {
            if ((OwningColumn.Site == null || !OwningColumn.Site.DesignMode) &&
(RowIndex < 0 || !IsInEditMode) && !String.IsNullOrEmpty(WatermarkText) &&
(GetValue(rowIndex) == null || GetValue(rowIndex) == DBNull.Value))

            {

                cellStyle.Font = new Font(cellStyle.Font, FontStyle.Italic);

                cellStyle.ForeColor = Color.Gray;

                formattedValue = WatermarkText;

            }

            base.Paint(graphics, clipBounds, cellBounds, rowIndex,

                cellState, value, formattedValue, errorText,

                cellStyle, advancedBorderStyle, paintParts);

        }

    }

}

Comments

  • Anonymous
    January 03, 2008
    Thank you for this how-to. One comment that may help others. Watch out for SharedRows - they will give you InvalidOperationExceptions when you check for IsInEditMode. To check if the row is being shared, make sure that "this.RowIndex >= 0" in the beginning of the Paint method. (Shared rows have a RowIndex == -1). Cheers,   Thies Schrader.

  • Anonymous
    January 03, 2008
    Good catch, Thies. Thanks! I updated the text and the code to take shared rows into consideration. The revised if statement enables the watermark to appear for shared rows, including the row for new records. -Karl