Watermarked edit controls

Posted December 1st @ 5:58 pm by aaron

Recently I wanted to use a WinForms TextBox with some “watermark text”, but had some trouble finding anything existing on the web. Which was surprising because of how ubiquitous they are. Turns out the terminology varies: cue, prompt, or watermark. And “watermark” is the least used.

Once I got my ducks in a row (by perusing the MSDN documentation on the Win32 edit control, which is the foundation of the .NET TextBox anyway) I found a few resources online, but they either a) didn’t wrap the exact Win32 edit control behavior, or b) were just a one-off “send a message to the control like this”, when I’d prefer a more polished derived control with designer support, etc.

So I present CueTextBox.cs, CueComboBox.cs, and CueToolStripTextBox.cs. Here’s a simple example that hosts all 3 of them:

image

And, in the interest of making this as easy as possible to get, here is the code. It’s really a very simple extension - I’m not sure why this wasn’t included in the BCF (but I can guess: this will only work on Windows XP and higher, and only if visual styles are enabled).
Note that the downloaded versions have the license text, but I’ve left it off this page (the license from the downloaded files applies). Why do you care? Let Jeff Atwood tell you.

kick it on DotNetKicks.com

CueTextbox.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Lerch.Samples
{
    public class CueTextBox : TextBox
    {
        #region PInvoke Helpers

        private static uint ECM_FIRST = 0×1500;
        private static uint EM_SETCUEBANNER = ECM_FIRST + 1;

        [DllImport(“user32.dll”, CharSet = CharSet.Auto, SetLastError = false)]
        private static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, String lParam);

        #endregion PInvoke Helpers

        #region CueText

        private string _cueText = String.Empty;

        /// <summary>
        /// Gets or sets the text the <see cref=”TextBox”/> will display as a cue to the user.
        /// </summary>
        [Description(“The text value to be displayed as a cue to the user.”)]
        [Category(“Appearance”)]
        [DefaultValue(“”)]
        [Localizable(true)]
        public string CueText
        {
            get { return _cueText; }
            set
            {
                if (value == null)
                {
                    value = String.Empty;
                }

                if (!_cueText.Equals(value, StringComparison.CurrentCulture))
                {
                    _cueText = value;
                    UpdateCue();
                    OnCueTextChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Occurs when the <see cref=”CueText”/> property value changes.
        /// </summary>
        public event EventHandler CueTextChanged;

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnCueTextChanged(EventArgs e)
        {
            EventHandler handler = CueTextChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion CueText

        #region ShowCueTextOnFocus

        private bool _showCueTextWithFocus = false;

        /// <summary>
        /// Gets or sets a value indicating whether the <see cref=”TextBox”/> will display the <see cref=”CueText”/>
        /// even when the control has focus.
        /// </summary>
        [Description(“Indicates whether the CueText will be displayed even when the control has focus.”)]
        [Category(“Appearance”)]
        [DefaultValue(false)]
        [Localizable(true)]
        public bool ShowCueTextWithFocus
        {
            get { return _showCueTextWithFocus; }
            set
            {
                if (_showCueTextWithFocus != value)
                {
                    _showCueTextWithFocus = value;
                    UpdateCue();
                    OnShowCueTextWithFocusChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Occurs when the <see cref=”ShowCueTextWithFocus”/> property value changes.
        /// </summary>
        public event EventHandler ShowCueTextWithFocusChanged;

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnShowCueTextWithFocusChanged(EventArgs e)
        {
            EventHandler handler = ShowCueTextWithFocusChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion ShowCueTextOnFocus

        #region Overrides

        protected override void OnHandleCreated(EventArgs e)
        {
            UpdateCue();

            base.OnHandleCreated(e);
        }

        #endregion Overrides

        private void UpdateCue()
        {
            // If the handle isn’t yet created, 
            // this will be called when it is created
            if (this.IsHandleCreated)
            {
                SendMessage(new HandleRef(this, this.Handle), EM_SETCUEBANNER, (_showCueTextWithFocus) ? new IntPtr(1) : IntPtr.Zero, _cueText);
            }
        }
    }
}

CueComboBox.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Lerch.Samples
{
    public class CueComboBox : ComboBox
    {
        #region PInvoke Helpers

        private static uint CB_SETCUEBANNER = 0×1703;

        [DllImport(“user32.dll”, CharSet = CharSet.Auto, SetLastError = false)]
        private static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, String lParam);

        #endregion PInvoke Helpers

        #region CueText

        private string _cueText = String.Empty;

        /// <summary>
        /// Gets or sets the text the <see cref=”ComboBox”/> will display as a cue to the user.
        /// </summary>
        [Description(“The text value to be displayed as a cue to the user.”)]
        [Category(“Appearance”)]
        [DefaultValue(“”)]
        [Localizable(true)]
        public string CueText
        {
            get { return _cueText; }
            set
            {
                if (value == null)
                {
                    value = String.Empty;
                }

                if (!_cueText.Equals(value, StringComparison.CurrentCulture))
                {
                    _cueText = value;
                    UpdateCue();
                    OnCueTextChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Occurs when the <see cref=”CueText”/> property value changes.
        /// </summary>
        public event EventHandler CueTextChanged;

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnCueTextChanged(EventArgs e)
        {
            EventHandler handler = CueTextChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion CueText

        #region Overrides

        protected override void OnHandleCreated(EventArgs e)
        {
            UpdateCue();

            base.OnHandleCreated(e);
        }

        #endregion Overrides

        private void UpdateCue()
        {
            // If the handle isn’t yet created, 
            // this will be called when it is created
            if (this.IsHandleCreated)
            {
                SendMessage(new HandleRef(this, this.Handle), CB_SETCUEBANNER, IntPtr.Zero, _cueText);
            }
        }
    }
}

CueToolStripTextBox.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Lerch.Samples
{
    public class CueToolStripTextBox : ToolStripTextBox
    {
        public CueToolStripTextBox()
            : base()
        {
            if (this.Control != null)
            {
                this.Control.HandleCreated += new EventHandler(OnControlHandleCreated);
            }
        }

        public CueToolStripTextBox(string name)
            : base(name)
        {
            if (this.Control != null)
            {
                this.Control.HandleCreated += new EventHandler(OnControlHandleCreated);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.Control != null)
                {
                    this.Control.HandleCreated -= new EventHandler(OnControlHandleCreated);
                }
            }

            base.Dispose(disposing);
        }

        void OnControlHandleCreated(object sender, EventArgs e)
        {
            UpdateCue();
        }

        #region PInvoke Helpers

        private static uint ECM_FIRST = 0×1500;
        private static uint EM_SETCUEBANNER = ECM_FIRST + 1;

        [DllImport(“user32.dll”, CharSet = CharSet.Auto, SetLastError = false)]
        private static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, String lParam);

        #endregion PInvoke Helpers

        #region CueText

        private string _cueText = String.Empty;

        /// <summary>
        /// Gets or sets the text the <see cref=”TextBox”/> will display as a cue to the user.
        /// </summary>
        [Description(“The text value to be displayed as a cue to the user.”)]
        [Category(“Appearance”)]
        [DefaultValue(“”)]
        [Localizable(true)]
        public string CueText
        {
            get { return _cueText; }
            set
            {
                if (value == null)
                {
                    value = String.Empty;
                }

                if (!_cueText.Equals(value, StringComparison.CurrentCulture))
                {
                    _cueText = value;
                    UpdateCue();
                    OnCueTextChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Occurs when the <see cref=”CueText”/> property value changes.
        /// </summary>
        public event EventHandler CueTextChanged;

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnCueTextChanged(EventArgs e)
        {
            EventHandler handler = CueTextChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion CueText

        #region ShowCueTextOnFocus

        private bool _showCueTextWithFocus = false;

        /// <summary>
        /// Gets or sets a value indicating whether the <see cref=”TextBox”/> will display the <see cref=”CueText”/>
        /// even when the control has focus.
        /// </summary>
        [Description(“Indicates whether the CueText will be displayed even when the control has focus.”)]
        [Category(“Appearance”)]
        [DefaultValue(false)]
        [Localizable(true)]
        public bool ShowCueTextWithFocus
        {
            get { return _showCueTextWithFocus; }
            set
            {
                if (_showCueTextWithFocus != value)
                {
                    _showCueTextWithFocus = value;
                    UpdateCue();
                    OnShowCueTextWithFocusChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Occurs when the <see cref=”ShowCueTextWithFocus”/> property value changes.
        /// </summary>
        public event EventHandler ShowCueTextWithFocusChanged;

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void OnShowCueTextWithFocusChanged(EventArgs e)
        {
            EventHandler handler = ShowCueTextWithFocusChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion ShowCueTextOnFocus

        private void UpdateCue()
        {
            // If the handle isn’t yet created, 
            // this will be called when it is created
            if ((this.Control != null) && (this.Control.IsHandleCreated))
            {
                SendMessage(new HandleRef(this.Control, this.Control.Handle), EM_SETCUEBANNER, (_showCueTextWithFocus) ? new IntPtr(1) : IntPtr.Zero, _cueText);
            }
        }
    }
}
Technorati Tags: ,

1 Trackbacks/Pingbacks

  1. Pingback: Links from December 3rd, 2007 « Eppur si muove on December 3, 2007

1 Comments

  1. Chris Miller
    February 1, 2008 at 15:15

    Awesome article but… the cue isn’t visible in multi-line mode. EM_SETCUEBANNER is even documented not to work with multi-line. There appears to be no work around AND still using this clean approach. :(

Leave a comment

OpenID Login

Standard Login

Options:

Size

Colors