There are a couple of things to note:
- There is already a mechanism for this in Windows called a
CueBanner
which is very easy to implement either from form code or as a custom control
- Your implementation doesn't quite behave or look the same as the Native implementation:
a) there is no visual cue (TB Cursor) until you hide the Panel
b) Your Panel/Cue Text doesn't disappear until you start typing. This seems to be what you want, but it is not how the native one typically works (as Visual Vincent points out in a comment, there is an option for this. I personally cant recall seeing it enough to register with me).
c) In the native implementation, the Cue Text/Watermark uses the same font as the control. Only a Combobox
set to DropDownList
uses Italic (maybe you never noticed this).
- Your app is leaking pretty badly in spots:
CreateWatermark
gets called repeatedly to redisplay the Watermark/CueText by creating a new Panel
each time, but you are not disposing of the old one. Remember: If you create a control in code, you also need to dispose of it rather than just removing it from the controls collection.
- The same is true for the
Font
object, although that will only matter if the Font
on the TextBox changes.
First, an answer to the question you asked - allowing Left, Right, Center TextAlignment.
Private Sub Painted(sender As Object, e As PaintEventArgs)
' this can all be moved to the CreateWatermark method
'WaterContainer.Location = New Point(0, 0)
'WaterContainer.Anchor = AnchorStyles.Left Or AnchorStyles.Right
'WaterContainer.Height = Me.Height
'WaterContainer.Width = Me.Width
Dim rect As New Rectangle(2, 1, Width - 8, Height - 2)
Dim TxtFlags As TextFormatFlags
Select Case TxtAlign
Case HorizontalAlignment.Center
TxtFlags = TextFormatFlags.HorizontalCenter
Case HorizontalAlignment.Left
TxtFlags = TextFormatFlags.Left
Case HorizontalAlignment.Right
TxtFlags = TextFormatFlags.Right
End Select
TextRenderer.DrawText(e.Graphics, WatermarkText, WFont, rect, WatermarkColor, TxtFlags)
End Sub
- This uses the
TextRenderer
for drawing the text which is usually a better choice for controls (save Graphics.DrawString()
for drawing to bitmaps and images)
- This version shows it based on a new
WatermarkTextAlign
property in case you want to allow it to vary from the TextBox
. If not, use parts of Visual Vincent's version or combine them.
Cue Banner
Partial Friend Class NativeMethods
...
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Friend Shared Function SendMessage(hWnd As IntPtr, msg As Integer,
wParam As Integer,
<MarshalAs(UnmanagedType.LPWStr)> lParam As String) As Int32
End Function
Private Const EM_SETCUEBANNER As Integer = &H1501
Private Const CB_SETCUEBANNER As Int32 = &H1703
Friend Shared Sub SetCueText(ctl As Control, text As String)
If TypeOf ctl Is ComboBox Then
SendMessage(ctl.Handle, CB_SETCUEBANNER, 0, text)
Else
SendMessage(ctl.Handle, EM_SETCUEBANNER, 0, text)
End If
End Sub
End Class
Usage is simple:
NativeMethods.SetCueText(tbPInvWM, "Enter text...")
NativeMethods.SetCueText(ComboBox1, "Select...")
You can also create a control which inherits from TextBox and implement it there (override OnHandleCreated
). This would allow the Cue Text to show at design time as your does, but with much less code.
Finally, there is an easier way to do it with an internal control:
- Rather than a
Panel
, use a Label
. You can use the label's Font
, TextAlign
, ForeColor
and Text
properties as the backing fields for the Properties you expose. The RightToLeft
setting could also be mirrored.
- To make the Watermark/Cue Text/Label disappear as needed, just set it to invisible
This would eliminate all issues related to disposing and drawing the text.
Public Class TextBoxEx
Inherits TextBox
Private WaterCtrl As Label
...
' Example of using label props as the backing field:
<Category("Watermark Attributes"),
Description("Sets Watermark Text"),
DefaultValue("Watermark Text")>
Public Property WatermarkText As String
Get
If WaterCtrl IsNot Nothing Then
Return WaterCtrl.Text
Else
Return ""
End If
End Get
Set(value As String)
If WaterCtrl IsNot Nothing Then
WaterCtrl.Text = value
End If
End Set
End Property
The Cue Text display is handled by making the label visible or not:
' when they click on the label, hide and
' activate the TB like a regular CueBanner does
Private Sub LblClick(sender As Object, e As EventArgs)
WaterCtrl.Visible = False
Me.Select()
End Sub
Protected Overrides Sub OnLeave(e As EventArgs)
MyBase.OnLeave(e)
WaterCtrl.Visible = (Text.Length = 0)
End Sub
Thats not all the code, but it should give you a start and it's both simpler and acts more like the native version.