You've probably seen these types of textboxes on your travels around the web, such as the eBay search box, which is a textbox with a prompt in it like "Enter your search string here". As soon as you click in the box, the prompt disappears, leaving an empty textbox where you can type your search string. Microsoft even has an AJAX example of this control on their Atlas web site, called the
More recently, this type of control has started to appear in Windows applications like Outlook 2007, IE7, and also Firefox 2.0. This control can be very handy, as it basically works like a
Textbox and a
onFocus events in order to put the prompt in the
value" property of the
The standard WinForms
TextBox does not support this functionality natively, so this class will address that shortcoming by inheriting from
System.Windows.Forms.TextBox and handling the display of the prompt text ourselves.
Using the code
WndProc may seem like overkill, but in this case the code turns out to be fairly simple. For this control, we only need to be concerned about the
WM_KILLFOCUS messages, which should be the same as the
SetFocus events, and
protected override void WndProc(ref System.Windows.Forms.Message m)
_drawPrompt = false;
_drawPrompt = true;
if (m.Msg == WM_PAINT && _drawPrompt && this.Text.Length == 0 &&
DrawTextPrompt does most of the work. It determines the client rectangle in which to draw the prompt and any offset based on the
HorizontalAlignment, then uses
TextRenderer to draw the
PromptText inside the rectangle:
protected virtual void DrawTextPrompt(Graphics g)
TextFormatFlags flags = TextFormatFlags.NoPadding |
TextFormatFlags.Top | TextFormatFlags.EndEllipsis;
Rectangle rect = this.ClientRectangle;
flags = flags | TextFormatFlags.HorizontalCenter;
flags = flags | TextFormatFlags.Left;
flags = flags | TextFormatFlags.Right;
TextRenderer.DrawText(g, _promptText, this.Font, rect,
_promptColor, this.BackColor, flags);
Points of Interest
My first thought when tackling this control was to override
OnLostFocus and just swap the
PromptText values (in addition to changing the
ForeColor). This immediately turned out to be a bad idea, because as soon as I tested it I noticed that the
Text property at design-time was suddenly replaced with the
PromptText (and so was the
ForeColor replaced with the
PromptForeColor). This effectively removed the ability for the developer to set a default
Text value, so it was time for a new approach.
After mulling over a couple of alternatives, I decided to override the
OnPaint method and just manually draw the prompt over top of the TextBox region. This would solve the "hack" nature of trying to manipulate the
ForeColor properties. So I developed the
DrawTextPrompt function and added a call in
OnPaint. Unfortunately, this also turned out to be a problematic solution (and I have left the code in so you can test it for yourself). The prompt would simply not draw properly over top of the
TextBox, displaying various weird behaviors such as disappearing text, incorrect fonts, etc. My first thought was that I had coded
DrawTextPrompt incorrectly, but a simple test showed that it was indeed drawing the prompt correctly.
So after pulling my hair out some more, I finally rolled up my sleeves and decided to override
WndProc. I knew that I would have to figure out which message was the key to the solution (
WM_PAINT was the obvious choice, but wasn't that what
OnPaint was supposed to do?). After a few hours of frustration, and not having any other candidates, I decided to try and see if
WM_PAINT would produce any different results. So I added the call to
DrawTextPrompt and, lo and behold, it worked!!! I don't yet have an explanation as to why
WM_PAINT works and
OnPaint doesn't, but you can try it for yourself by un-commenting the
SetStyle line in the constructor. My working theory is that the
SetStyle call disables additional behavior that I have not accounted for.
As you go through the code, notice all the places where there is a call to
Invalidate(). This is so the control will redraw itself as the design-time properties change. The control should behave the same at design-time as it does at run-time. If there is a value in the
Text property, the
PromptText will not be displayed. If you change the
PromptForeColor or the
TextAlign properties, the control should change right away.
I also added a
FocusSelect boolean property (a feature I have longed for since VB3, where inheritance was not an option) that, when set to
true, will select all the text in the control when it receives the focus.
Compared to EM_SETCUEBANNER
As pointed out in the user comments below, Windows XP and newer has a message that you can send to a
TextBox control that will accomplish almost the same goal, and that is
EM_SETCUEBANNER. Using this message has a few advantages:
- Also supported by the
- Forward compatibility with future versions of Windows
- Better support for different UI enhancements and layouts, including things like themes, Aero, TabletPC, etc.
But, there are also some disadvantages:
- Only supported on Windows XP and newer.
- Windows CE/Mobile is not supported.
- Doesn't work with a multiline
- Developer must also include a manifest for Comctl32.dll with the EXE.
- Developer has no control over the prompt's font properties or color.
- Apparently, there is a bug that causes
EM_SETCUEBANNER not to work if you install one of the Asian language packs on XP.
I haven't verified that
PromptedTextBox runs on all the older platforms (or CE), but the approach doesn't require anything special. In fact, the theory should apply as far back as Windows 3.1, but of course .NET only supports Windows 98 and above.
In short, if you find a situation where
PromptedTextBox doesn't behave as expected and you have the option, see if
EM_SETCUEBANNER will do the trick. If not, drop me a line and I will see what I can do.
- Version 1.0: 04-Oct-2006 - Initial posting.
- Version 1.1: 16-Oct-2006 - Added the
PromptFont property to allow more developer control of the prompt display. Also changed the default prompt color to