The .NET framework is huge, but not so huge that it does everything for everyone; there are things that they in Redmond miss or don’t do for whatever reason but is still generally applicable to many developers. So, dear reader, I present to you a new, hopefully weekly, series of posts on stuff I find missing in .NET, typically where even the Google fails to find the answer.
One of the subtle UI improvements we’ve seen over the past few windows versions, and on the web, is the cue banner, or text prompt. They are hardly groundbreaking — putting them in your app probably won’t win you awards, but they do serve to make your applications that much more polished and usable.
They are used increasingly more often now that Vista has come out, which solved a few problems that I’ll address later. You can see them used in the popular browsers IE 7 and Firefox in the search boxes, denoting the search engine of choice for the user in a subtle grey tone. Click inside those textboxes, however, and the text disappears. That’s all a cue banner is, that grey text, informing the user what exactly she is supposed to use the text control for.
Cue banners work great in places where a formatted string is required, say an email address or a URL or a phone number. Instead of cluttering up the window with tons of labels, explaining examples or ranges of valid values, you can put those in a cue banner. It makes the UI a tad more elegant and self-explanatory.
These aren’t exactly missing on the web though. Search for ‘cue banner’ in your favourite search engine and you’ll find all kinds of examples, but none of them cover everything, or in a way that seems satisfactory to me. An example of an inherited textbox for Winforms can be found at this Channel 9 post. Daniel Moth has a version that works for the .NET Compact Framework as well as the main framework and decided to eschew the OS support and do it completely in managed code. He’s braver than I; I’d prefer to have MS do all that work and then use it.
[ad]
The Code
Download the code I’ve covered in this article.
OK, so now we know what a cue banner is, how do we include them in our apps?
The first thing to remember is that this is all done in Win32 which requires P/Invoke. All we’re doing is sending a Win32 message on the window handle of the control. That’s pretty straightforward boilerplate code that you can find on pinvoke.net. The one thing you may have to search for is the value of the msg parameter, EM_SETCUEBANNER, but I got you covered.
private const int EM_SETCUEBANNER = 0x1501; private const int EM_GETCUEBANNER = 0x1502; [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern Int32 SendMessage(IntPtr hWnd, int msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
You can certainly do what buddy did on Channel 9: inherit from TextBox, add the required property, call the P/Invoke method and call it a day but then you’d be missing something important. Cue banners can be placed on edit controls, not just textboxes. The unfortunate thing about WinForms is that it’s essentially a facade over Win32. It does a very good job, make no mistake, but if you push it in the wrong place you end up exposing what’s underneath. And what’s underneath is Win32 which identifies all controls with its corresponding HWND. As a consequence, Windows Forms has a pretty flat hierarchy (cf. with WPF which was designed when OO was the prevailing wisdom; a very deep hierarch). Therefore, if you want to display a cue banner in a ComboBox or a RichTextBox, which the OS allows, then you’d have to subclass ComboBox and RichTextBox. Well, that’s just not scalable or portable. You’d always have to import your own controls and use those in your projects. I’ve found that most custom controls suck when used in the designer, so who wants to use them?
Besides, all the info that’s required to make cue banners work is already there in the control! So let’s use it instead. It’s not ideal, but I prefer to keep it all in a static class. We only need to use the Handle property which Control owns and we should also parameterize the actual cue banner text so we write the following method to set cue banners:
public static void SetCueText(Control control, string text) { SendMessage(control.Handle, EM_SETCUEBANNER, 0, text); }
Easy or what? So now drop a text box on a form. Call SetCueText in the form constructor for the text box and you’re set. Now drop a ComboBox and do the same for it. Hit F5 and prepare to bask in the glo… Hey! Why isn’t the cue banner set in the ComboBox? This is where other articles completely fall down on this stuff. You see, the ComboBox is actually made up of three controls: a text box and a list box and a button; think of it as a really old and ubiquitous UserControl. ComboBox.Handle is the handle for the combined control, not the text control, so we’ll need a way to get its handle. For that, we need more P/Invoke:
[DllImport("user32.dll")] private static extern bool GetComboBoxInfo(IntPtr hwnd, ref COMBOBOXINFO pcbi); [StructLayout(LayoutKind.Sequential)] private struct COMBOBOXINFO { public int cbSize; public RECT rcItem; public RECT rcButton; public IntPtr stateButton; public IntPtr hwndCombo; public IntPtr hwndItem; public IntPtr hwndList; } [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; }
OK, now we’re ready to add some more logic to the SetCueText method above:
public static void SetCueText(Control control, string text) { if (control is ComboBox) { COMBOBOXINFO info = GetComboBoxInfo(control); SendMessage(info.hwndItem, EM_SETCUEBANNER, 0, text); } else { SendMessage(control.Handle, EM_SETCUEBANNER, 0, text); } } private static COMBOBOXINFO GetComboBoxInfo(Control control) { COMBOBOXINFO info = new COMBOBOXINFO(); //a combobox is made up of three controls, a button, a list and textbox; //we want the textbox info.cbSize = Marshal.SizeOf(info); GetComboBoxInfo(control.Handle, ref info); return info; }
Hit F5 on your test app again, and voila: cue banner on ComboBox!
Notes
This only works on Windows XP and Vista.
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.
On Windows Vista, they’ve added a use for wParam parameter in the SendMessage() call. If you set it to TRUE, then the cue banner text will remain when the control gets focus.
I’ve found no use for the corresponding GetCueText, but if you want it, make sure you use a StringBuilder as the last parameter in SendMessage(), which requires another declaration of SendMessage.
Important: If you’ve done everything above and you still don’t see the wonderful cue banner text, check to see that before you call Application.Run(), you call Application.EnableVisualStyles(). That has to be called else cue banner won’t show up no matter what OS your are using.
Exercises
A few exercises for the reader:
- SetCueText() is an ideal candidate for an extension method, if you’re allowed to use .NET 3.5. It’ll make the code just a little more readable. I’ll let you figure out how to do that.
- There is a way you can have this on the designer. Just provide a component that implements IExtenderProvider to be dropped on the design surface.
Fantastic !!
I am using VS 2008 on Vista Ultimate. It gives the same problem as for Win XP, i.e. it shows Cue Text in some East Asian language. I have checked my system. No language pack has been installed. Otherwise it works fine especially your tip for wparam. Can you help ?
@Jaya Labo: The problem in Windows XP isn’t that the cue banner shows up in an East Asian Language; it’s that if an East Asian language pack is installed, then the cue banner doesn’t show up at all. Essentially, you have to pick one or the other.
If you’re seeing the cue text, but it shows up as a different language, it might be an encoding problem. Are you running on a 32-bit or 64-bit processor?
Post the relevant code, and I might be able to help you.
Thanks for the immediate response !
It is on a 32 bit processor and the relevant code in VB.net is
Public Class LabelTextBox
Inherits TextBox
Private Shared ECM_FIRST As UInteger = 5376
Private Shared EM_SETCUEBANNER As UInteger = ECM_FIRST + 1
Private _promptText As String = [String].Empty
_
_
_
Public Property PromptText() As String
Get
Return _promptText
End Get
Set(ByVal value As String)
_promptText = value
UpdatePrompt()
End Set
End Property
Private Sub UpdatePrompt()
If Me.IsHandleCreated Then
SendMessage(New HandleRef(Me, Me.Handle), EM_SETCUEBANNER, New IntPtr(1), _promptText)
End If
End Sub
End Class
I forgot one line in the previous reply
Declare Function SendMessage Lib “user32.dll” Alias “SendMessageA” (ByVal hWnd As HandleRef, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
Sorry for that.
@Jaya Labo: OK, we found the problem I think: SendMessageA uses ANSI text. The string for EM_SETCUEBANNER must be Unicode. Try one of the following in your declaration of SendMessage:
Declare Function SendMessage Lib “user32.dll” Alias “SendMessage” (…)
or
Declare Function SendMessage Lib “user32.dll” Alias “SendMessageW” (…)
Either of those should work, however, let me recommend the first one. In general you want the Function name to correspond to the Alias name to keep them clear in your mind. Hopefully this works for you!
Hi
Thanks a lot !! You are simple great.
It worked. I changed the sendmessage code to
_
Public Shared Function SendMessage(ByVal hWnd As HandleRef, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
End Function
Actually the message should have read like this
Hi
Thanks a lot !! You are simply great.
It worked. I changed the sendmessage code to
” _
Public Shared Function SendMessage(ByVal hWnd As HandleRef, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
End Function”
You can to using CB_SETCUEBANNER message for ComboBox instead of call GetComboBoxInfo and COMBOBOXINFO. It is simplier
comboBox1.Items.Add(“Dollar”);
comboBox1.Items.Add(“Euro”);
comboBox1.Items.Add(“Rubel”);
// set banner
SendMessage(comboBox1.Handle, CB_SETCUEBANNER, 0, “Choose…”);
Is there a way to use this when the DropDownStyle is set to DropDownList?
The definition of the COMBOBOXINFO structure isn’t quite right and won’t work on 64bit. stateButton should be defined as int (or uint) rather than IntPtr.
I was having trouble getting this working on the project I work on which has been upgraded from 2005 to 2008 to 2010. I discovered that you must have the Common Controls dependency referenced in your app.manifest:
Thought this might be useful to someone.