Custom Watermarked Textbox behaving strangely
-
20-08-2019 - |
Question
SOLUTION:
Thanks to Patrick below, I have refactored the C# CodeProject version into a VB.NET version that works for me. Hopefully it can help you guys as well:
Partial Public Class WatermarkedTextBox
Inherits TextBox
Private _waterMarkColor As Color = Color.LightGray
Public Property WaterMarkColor() As Color
Get
Return _waterMarkColor
End Get
Set(ByVal value As Color)
_waterMarkColor = value
End Set
End Property
Private _waterMarkText As String = "Watermark"
Public Property WaterMarkText() As String
Get
Return _waterMarkText
End Get
Set(ByVal value As String)
_waterMarkText = value
End Set
End Property
Sub New()
End Sub
Protected Overloads Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
Const WM_PAINT As Integer = &HF
If m.Msg = WM_PAINT Then
If Text.Length <> 0 Then
Return
End If
Using g As Graphics = Me.CreateGraphics
g.DrawString(WaterMarkText, Me.Font, New SolidBrush(WaterMarkColor), 0, 0)
End Using
End If
End Sub
Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
MyBase.OnTextChanged(e)
Invalidate()
End Sub
Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
MyBase.OnLostFocus(e)
Invalidate()
End Sub
Protected Overrides Sub OnFontChanged(ByVal e As System.EventArgs)
MyBase.OnFontChanged(e)
Invalidate()
End Sub
Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
'' added so the watermark is not cleared until text is entered
MyBase.OnGotFocus(e)
Invalidate()
End Sub
End Class
I am building a kiosk application, and in order to keep it aesthetically pleasing I decided to implement a watermarked textbox as the entry fields. I found this project on CodeProject, converted it over to VB.NET and put it into my application. It works, in the sense that it watermarks and clears them just fine, but when I go to enter text this happens:
Empty:
Filled:
So basically it is not clearing the whole watermark, just the space appropriated for the default height of a textbox. here is the converted code:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Forms
Imports System.Drawing
Partial Public Class WatermarkedTextBox
Inherits TextBox
Private oldFont As Font = Nothing
Private waterMarkTextEnabled As Boolean = False
#Region "Attributes"
Private _waterMarkColor As Color = Color.LightGray
Public Property WaterMarkColor() As Color
Get
Return _waterMarkColor
End Get
Set(ByVal value As Color)
_waterMarkColor = value
Invalidate()
End Set
End Property
Private _waterMarkText As String = "Water Mark"
Public Property WaterMarkText() As String
Get
Return _waterMarkText
End Get
Set(ByVal value As String)
_waterMarkText = value
Invalidate()
End Set
End Property
#End Region
Public Sub New()
JoinEvents(True)
End Sub
Protected Overloads Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
WaterMark_Toggle(Nothing, Nothing)
End Sub
Protected Overloads Overrides Sub OnPaint(ByVal args As PaintEventArgs)
'Dim drawFont As New System.Drawing.Font(Font.FontFamily, Font.Size, Font.Style, Font.Unit)
Dim drawFont As New Font("Arial", 28, FontStyle.Bold) 'New System.Drawing.Font(oldFont.FontFamily, oldFont.Size, oldFont.Style, oldFont.Unit)
Dim drawBrush As New SolidBrush(WaterMarkColor)
args.Graphics.DrawString((If(waterMarkTextEnabled, WaterMarkText, Text)), drawFont, drawBrush, New PointF(0.0F, 0.0F))
MyBase.OnPaint(args)
End Sub
Private Sub JoinEvents(ByVal join As Boolean)
If join Then
AddHandler Me.TextChanged, AddressOf WaterMark_Toggle
AddHandler Me.LostFocus, AddressOf Me.WaterMark_Toggle
AddHandler Me.FontChanged, AddressOf Me.WaterMark_FontChanged
End If
End Sub
Private Sub WaterMark_Toggle(ByVal sender As Object, ByVal args As EventArgs)
If Me.Text.Length <= 0 Then
EnableWaterMark()
Else
DisableWaterMark()
End If
End Sub
Private Sub EnableWaterMark()
oldFont = New System.Drawing.Font(Font.FontFamily, Font.Size, Font.Style, Font.Unit)
Me.SetStyle(ControlStyles.UserPaint, True)
Me.waterMarkTextEnabled = True
Refresh()
End Sub
Private Sub DisableWaterMark()
Me.waterMarkTextEnabled = False
Me.SetStyle(ControlStyles.UserPaint, False)
If oldFont IsNot Nothing Then
Me.Font = New System.Drawing.Font(oldFont.FontFamily, oldFont.Size, oldFont.Style, oldFont.Unit)
End If
End Sub
Private Sub WaterMark_FontChanged(ByVal sender As Object, ByVal args As EventArgs)
If waterMarkTextEnabled Then
oldFont = New System.Drawing.Font(Font.FontFamily, Font.Size, Font.Style, Font.Unit)
Refresh()
End If
End Sub
End Class
I attempted to force the class to use my set font size in the OnPaint event, but no luck with that. Is there something else that I am missing that is making this more difficult than it should be?
Thanks!
Solution
You're just missing a Refresh at the end of the DisableWaterMark sub:
Private Sub DisableWaterMark()
Me.waterMarkTextEnabled = False
Me.SetStyle(ControlStyles.UserPaint, False)
If oldFont IsNot Nothing Then
Me.Font = New System.Drawing.Font(oldFont.FontFamily, oldFont.Size, oldFont.Style, oldFont.Unit)
End If
Refresh()
End Sub
EDIT:
Rather than using the UserPaint control style, you can handle the WM_PAINT message in WndProc, and only print the watermark if the text is empty. The result is basically the same though.
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
Const WM_PAINT As Integer = &HF
If m.Msg = WM_PAINT Then
If Text.Length <> 0 Then
Return
End If
Using g As Graphics = Me.CreateGraphics
g.DrawString("Water Mark", Me.Font, Brushes.LightGray, 0, 0)
End Using
End If
End Sub
Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
MyBase.OnTextChanged(e)
Invalidate()
End Sub
Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
MyBase.OnLostFocus(e)
Invalidate()
End Sub
Protected Overrides Sub OnFontChanged(ByVal e As System.EventArgs)
MyBase.OnFontChanged(e)
Invalidate()
End Sub