Watermarked edit controls
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:
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.
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 = 0x1500; 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 = 0x1703; [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 = 0x1500; 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); } } } }
December 3rd, 2007 at 9:10 am
[...] Links from December 3rd, 2007 Watermarked edit controls [...]
February 1st, 2008 at 3:15 pm
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.
January 4th, 2009 at 10:42 pm
I found it doesn’t work! I drag the control to a form, and set the “cueText” property, then run the project, it happens nothing.
could you tell me why? my OS is Windows xp Sp3.
Thank you!
January 4th, 2009 at 10:46 pm
Try adding a 2nd control (button, or whatever). I think that when you have a single control it gets the focus, and if you just set the cue text property only (in the designer) it won’t show the text when the control has the focus.
January 5th, 2009 at 1:44 am
On Windows XP, this conflicts with the East Asian Language packs, so if you have them installed, cue banners won’t work. Vista fixes this.
I am in China, so it doesn’t work!
January 5th, 2009 at 8:27 am
Thanks for the find, and updating us with the info!
December 8th, 2009 at 11:32 am
I believe that these messages & functionality (at least CB_SETCUEBANNER) only apply to Windows Vista and beyond. So if you’re using XP (as I am) you’re out of luck.
- Matthew