Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
234 views
in Technique[技术] by (71.8m points)

.net - watermark Text-box Align Center

I'm using custom control watermark textbox and it's perfect, but the problem is that I can't change the watermark textAlign to center or right to left. By default it's left to right, how can I make it center or right to left?

This is the code :

Imports System.ComponentModel

Public Class Watermark
Inherits TextBox

'Declare A Few Variables
Dim WaterText As String
Dim WaterColor As Color
Dim WaterFont As Font
Dim WaterBrush As SolidBrush
Dim WaterContainer As Panel

Public Sub New()
    MyBase.New()
    StartProcess()
End Sub

Private Sub StartProcess()
    'Assign Values To the Variables
    WaterText = "Default Watermark"
    WaterColor = Color.Gray
    WaterFont = New Font(Me.Font, FontStyle.Italic)
    WaterBrush = New SolidBrush(WaterColor)

    CreateWatermark()

    AddHandler TextChanged, AddressOf ChangeText
    AddHandler FontChanged, AddressOf ChangeFont
End Sub

Private Sub CreateWatermark()
    WaterContainer = New Panel
    Me.Controls.Add(WaterContainer)
    AddHandler WaterContainer.Click, AddressOf Clicked
    AddHandler WaterContainer.Paint, AddressOf Painted
End Sub

Private Sub RemoveWatermark()
    Me.Controls.Remove(WaterContainer)
End Sub

Private Sub ChangeText(sender As Object, e As EventArgs)
    If Me.TextLength <= 0 Then
        CreateWatermark()
    ElseIf Me.TextLength > 0 Then
        RemoveWatermark()
    End If
End Sub

Private Sub ChangeFont(sender As Object, e As EventArgs)
    WaterFont = New Font(Me.Font, FontStyle.Italic)
End Sub

Private Sub Clicked(sender As Object, e As EventArgs)
    Me.Focus()
End Sub

Private Sub Painted(sender As Object, e As PaintEventArgs)
    WaterContainer.Location = New Point(2, 0)
    WaterContainer.Anchor = AnchorStyles.Left Or AnchorStyles.Right
    WaterContainer.Height = Me.Height
    WaterContainer.Width = Me.Width
    WaterBrush = New SolidBrush(WaterColor)

    Dim Graphic As Graphics = e.Graphics
    Graphic.DrawString(WaterText, WaterFont, WaterBrush, New PointF(-2.0!, 1.0!))
End Sub

Protected Overrides Sub OnInvalidated(e As System.Windows.Forms.InvalidateEventArgs)
    MyBase.OnInvalidated(e)
    WaterContainer.Invalidate()
End Sub

<Category("Watermark Attributes"), Description("Sets Watermark Text")> Public Property WatermarkText As String
    Get
        Return WaterText
    End Get
    Set(value As String)
        WaterText = value
        Me.Invalidate()
    End Set
End Property

<Category("Watermark Attributes"), Description("Sets Watermark Color")> Public Property WatermarkColor As Color
    Get
        Return WaterColor
    End Get
    Set(value As Color)
        WaterColor = value
        Me.Invalidate()
    End Set
End Property
End Class
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...