Search code examples
comboboxwindows-xpwatermark

Set ComboBox cue banner in Windows XP


I'm updating a VB.net project that needs to have cue banners added to text boxes and read-only comboboxes (DropDownStyle=DropDownList). The machine I'm developing on is Windows 7. I'm adding the cue text in a class that extends a combobox and adds a cue text property. This is how the cue text is added to the combobox:

 '"Me" refers to a combobox that has been extended to include a Cue Text property
 SendMessage(New HandleRef(Me, Me.Handle), CB_SETCUEBANNER, IntPtr.Zero, _cueText)

The above code is from this blog bost: http://www.aaronlerch.com/blog/page/7/, which is in C#; I translated it to VB. I tried other similar variations that I found elsewhere, all with the same result: it works great for text boxes and comboboxes when I run the program in Windows 7; it only works for text boxes in Windows XP.

I've read lots of comments on different forums about making sure visual styles are selected and disabling east Asia languages and complex scripts. I've done all that, but still haven't gotten it to work on XP.

Has anyone gotten cue banners for comboboxes to work on XP?


Solution

  • Using various blog and forum posts, I created a class that extends the ComboBox control and implements a CueText property that works on Windows 7 and XP. I found the most relevant pieces of information here:

    1. How to set cue text in XP: http://www.ageektrapped.com/blog/the-missing-net-1-cue-banners-in-windows-forms-em_setcuebanner-text-prompt/
    2. How to set cue text in Windows 7: http://www.aaronlerch.com/blog/2007/12/01/watermarked-edit-controls/

    In a nutshell, Windows 7 and XP set the cue banner text slightly differently, so you need to check which operating system the program is running on and then handle the cue text appropriately. You need to use EM_SETCUEBANNER As Integer = &H1501 for XP and CB_SETCUEBANNER As UInteger = &H1703 for Windows 7. You also need to single out the text part of the combo box if the app is running on XP. You can see the details in the code below. To figure out which OS is running, see MS KB articles 304289 (VB) or 304283 (C#). (I would post the links, but I don't have enough reputation points to post more than two.)

    One caveat is that this will not work on XP if the combo boxes are read only (DropDownStyle = DropDownList). Windows 7 seems to work fine either way. If your application needs to run on XP and you need the combo boxes to be read only but still display the cue text, here's what you can do:

    1. Create a combo box and use the default DropDownStyle, "DropDown"
    2. Handle the KeyPress event for the combo box(es) and in that method tell it that the event has been handled like this: e.Handled = True. This will prevent text from being typed.
    3. Create a blank ContextMenuStrip using the Toolbox in the design view, click on the combo box, view its properties, and set its ContextMenuStrip to the one you just created. This will cause nothing to happen when the user right clicks in the combo box so they can't paste text into it.

    Here's the VB code for a class that inherits the ComboBox control and adds a CueText property that works for XP and 7. The only thing you'll have to do is figure out which OS is running:

    Imports System.ComponentModel
    Imports System.Runtime.InteropServices
    
    Public Class CueComboBox
    Inherits ComboBox
    
    ' Occurs when the CueText property value changes.
    Public Event CueTextChanged As EventHandler
    
    'Windows XP
    Private Shared EM_SETCUEBANNER As Integer = &H1501
    'Windows 7
    Private Shared CB_SETCUEBANNER As UInteger = &H1703
    
    <DllImport("user32.dll", CharSet:=CharSet.Auto)> _
    Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal        wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As Int32
    End Function
    
    <DllImport("user32.dll")> _
    Private Shared Function GetComboBoxInfo(ByVal hwnd As IntPtr, ByRef pcbi As COMBOBOXINFO) As Boolean
    End Function
    
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure COMBOBOXINFO
        Public cbSize As Integer
        Public rcItem As RECT
        Public rcButton As RECT
        Public stateButton As IntPtr
        Public hwndCombo As IntPtr
        Public hwndItem As IntPtr
        Public hwndList As IntPtr
    End Structure
    
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure RECT
        Public left As Integer
        Public top As Integer
        Public right As Integer
        Public bottom As Integer
    End Structure
    
    Private Shared Function GetComboBoxInfo(ByVal control As Control) As COMBOBOXINFO
        Dim info As 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, info)
        Return info
    End Function
    
    
    Private _cueText As String = [String].Empty
    
    ' Gets or sets the text that will display as a cue to the user.
    <Description("The text value to be displayed as a cue to the user.")> _
    <Category("Appearance")> <DefaultValue("")> <Localizable(True)> _
    Public Property CueText() As String
        Get
            Return _cueText
        End Get
        Set(ByVal value As String)
            If value Is Nothing Then
                value = [String].Empty
            End If
    
            If Not _cueText.Equals(value, StringComparison.CurrentCulture) Then
                _cueText = value
                UpdateCue()
                OnCueTextChanged(EventArgs.Empty)
            End If
        End Set
    End Property
    
    <EditorBrowsable(EditorBrowsableState.Advanced)> _
    Protected Overridable Sub OnCueTextChanged(ByVal e As EventArgs)
        RaiseEvent CueTextChanged(Me, e)
    End Sub
    
    
    Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
        UpdateCue()
        MyBase.OnHandleCreated(e)
    End Sub
    
    
    Private Sub UpdateCue()
        ' If the handle isn't yet created, this will be called when it is created
        If Me.IsHandleCreated Then
            ' Windows XP sets the cue banner differently than Windows 7
            If Form1.OPERATING_SYSTEM = "Windows XP" Then
                Dim info As COMBOBOXINFO = GetComboBoxInfo(Me)
                SendMessage(info.hwndItem, EM_SETCUEBANNER, 0, _cueText)
            Else
                SendMessage(New HandleRef(Me, Me.Handle), CB_SETCUEBANNER, IntPtr.Zero, _cueText)
            End If
        End If
    End Sub
    
    End Class