Search code examples
.netvb.netinheritancetextboxonpaint

Creating a TextBox with watermark using ControlStyles.UserPaint shows the watermark just once at component creation


I work on a control inheriting from TextBox. I want it to feature a watermark property (the text you see when there is no text).

All steps taken:

In a new Visual Studio instance, click the link Create New Project, select the project type Windows Forms Control Library, name the project TBW1 (TextBox with Watermark), and click OK. Rename the default control to UTextBoxWatermark.

I want to inherit from the TextBox control, but new user controls inherit from the UserControl class by default. This is defined in the designer.

To access and modify the designer, at the top of Solution Explorer, click Show All Files. Expand UTextBoxWatermark.vb. Doubleclick CTextBoxWatermark.Designer.vb to open it in the code editor.

Replace the base class Control from UserControl

Partial Class UTextBoxWatermark
    Inherits System.Windows.Forms.UserControl
    ...
End Class

to TextBox

Partial Class UTextBoxWatermark
    Inherits System.Windows.Forms.TextBox
    ...
End Class

In the InitializeComponent procedure, remove the AutoScaleMode assignment. It does not exist in the TextBox control.

Private Sub InitializeComponent()
    components = New System.ComponentModel.Container()
End Sub

Close CTextBoxWatermark.Designer.vb. Use Save All to save the new project in your projects main folder.

The user control designer is not available anymore, because it will be painted from the inherited class, i.e., TextBox. Open CTextBoxWatermark.vb in the code editor. It is here that the extended functionality is implemented.

I want to add 2 properties: one for the text to be shown when the Text property contains a 0-length string, and one for the color in which this text will be drawn.

Public Class UTextBoxWatermark
    '============================================================================
    'VARIABLES.
    '============================================================================
    Private gsWatermarkText As String
    Private glWatermarkColor As Color

    '============================================================================
    'PROPERTIES.
    '============================================================================
    Public Property WatermarkText As String
        Get
            Return gsWatermarkText
        End Get
        Set(sValue As String)
            gsWatermarkText = sValue
        End Set
    End Property

    Public Property WatermarkColor As Color
        Get
            Return glWatermarkColor
        End Get
        Set(lValue As Color)
            glWatermarkColor = lValue
        End Set
    End Property
End Class

To draw the text the OnPaint event is overridden. For text boxes, this event is not called, unless the ControlStyles.UserPaint property is set to True in the constructor. If true, the control paints itself rather than the OS doing so.

Public Class UTextBoxWatermark
    ...

    '============================================================================
    'CONSTRUCTORS AND DESTRUCTORS.
    '============================================================================
    Public Sub New()
        'This call is required by the designer.
        InitializeComponent()

        SetStyle(ControlStyles.UserPaint, True)
    End Sub

    '============================================================================
    'EVENT HANDLERS.
    '============================================================================
    Protected Overrides Sub OnPaint(
        ByVal e As System.Windows.Forms.PaintEventArgs)

        Dim oBrush As SolidBrush

        'If the text is empty now, the watermark text should be written instead.
        If Me.Text.Length = 0 Then
            oBrush = New SolidBrush(glWatermarkColor)
            e.Graphics.DrawString(gsWatermarkText, Me.Font, oBrush, 0, 0)
        End If
    End Sub
End Class

In order to test the component, it must be built now.

Add a new project to this solution via File > Add > New Project. Select Windows Forms App and name the project TBW1_Client. Right-click it in the solutions explorer and select Set as Startup Project.

Add a reference to the text box with watermark project via Project > TBW1_Client Properties > References > Add > Browse > [Path to TBW1] > bin > Debug >TBW1.dll > OK.

Build the project. The control is available in the toolbox now. Doubleclick it to obtain a control in the test window. It should look exactly like an ordinary TextBox control.

Click on the control and check its properties in the properties window. The two newly defined properties WatermarkColor and WatermarkText will be shown near the end of the properties list. To test the functionality, provide a distinct color, for example red, and a text reading, for instance, "Type here".

It works! However, there are two problems:

(1) It works just once when the control is shown for the first time. Typing something and then removing the text leaves the text box empty. I would want to see the watermark text again.

(2) When displaying the watermark text for the first time, the proper font (as inherited from the form) is shown. When starting to type, an ugly system font is used.

How can I solve these 2 problems?

Edit

As per comment from VisualVincent, I improved OnPaint:

Protected Overrides Sub OnPaint(
    ByVal e As System.Windows.Forms.PaintEventArgs)

    Dim oBrush As SolidBrush

    MyBase.OnPaint(e)

    'If the text is empty now, the watermark text should be written instead.
    If Me.Text.Length = 0 Then
        oBrush = New SolidBrush(glWatermarkColor)
        e.Graphics.DrawString(gsWatermarkText, Me.Font, oBrush, 0, 0)
    Else
        oBrush = New SolidBrush(Me.ForeColor)
        e.Graphics.DrawString(Me.Text, Me.Font, oBrush, 0, 0)
    End If
End Sub

and added

Private Sub UTextBoxWatermark_TextChanged(sender As Object, e As EventArgs) _
    Handles Me.TextChanged

    Me.Invalidate()
End Sub

Watermark appears. When starting to write, the text appears, still in ugly System font, though. When I hover with the mouse over it, the text disappers.

When I remove the text, the watermark does not re-appear, unless I hover with the mouse over it.


Solution

  • Edit: Resurrected due to OP giving up on custom fore color requirement.

    This feature is supported by the native edit control that the WinForm Textbox class wraps. It is supported in Windows Vista and greater if visual styles are enabled.

    reference: EM_SETCUEBANNER message

    Example:

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            SetWaterMark(TextBox1, "Enter Something Here", True)
        End Sub
    
        <DllImport("user32.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Boolean, ByVal lParam As String) As Boolean
        End Function
    
        Private Shared Sub SetWaterMark(tb As TextBox, waterMarkText As String, Optional showIfFocused As Boolean = True)
            Const ECM_FIRST As Int32 = &H1500
            Const EM_SETCUEBANNER As Int32 = ECM_FIRST + 1
            If VisualStyles.VisualStyleInformation.IsEnabledByUser Then
                SendMessage(tb.Handle, EM_SETCUEBANNER, showIfFocused, waterMarkText)
            End If
        End Sub
    End Class