使用 ControlStyles.UserPaint 创建带水印的文本框仅在组件创建时显示一次水印

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

我正在处理从 TextBox 继承的控件。我希望它带有水印 属性(没有文字时您看到的文字)。

采取的所有步骤:

在一个新的Visual Studio实例中,单击link创建新项目,select项目类型WindowsForms Control Library,将项目命名为TBW1(TextBox with水印),然后单击确定。将默认控件重命名为 UTextBoxWatermark。

我想从 TextBox 控件继承,但新的用户控件默认从 UserControl class 继承。这是在设计器中定义的。

要访问和修改设计器,请在解决方案资源管理器的顶部单击“显示所有文件”。展开 UTextBoxWatermark.vb。双击 CTextBoxWatermark.Designer.vb 在代码编辑器中打开它。

从 UserControl

替换基础 class 控件
Partial Class UTextBoxWatermark
    Inherits System.Windows.Forms.UserControl
    ...
End Class

到文本框

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

在 InitializeComponent 过程中,删除 AutoScaleMode 分配。它不存在于 TextBox 控件中。

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

关闭CTextBoxWatermark.Designer.vb。使用全部保存将新项目保存在项目主文件夹中。

用户控件设计器不再可用,因为它将从继承的 class,即 TextBox 中绘制。在代码编辑器中打开 CTextBoxWatermark.vb。正是在这里实现了扩展功能。

我想添加 2 个属性:一个用于当文本 属性 包含长度为 0 的字符串时要显示的文本,一个用于绘制此文本的颜色。

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

要绘制文本,将重写 OnPaint 事件。对于文本框,不会调用此事件,除非在构造函数中将 ControlStyles.UserPaint 属性 设置为 True。如果为 true,控件将绘制自身而不是 OS 这样做。

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

为了测试组件,现在必须构建它。

通过“文件”>“添加”>“新建项目”向此解决方案添加一个新项目。 Select Windows Forms App 并将项目命名为 TBW1_Client。在解决方案资源管理器中右键单击它并 select Set as Startup Project.

通过项目 > TBW1_Client 属性 > 引用 > 添加 > 浏览 > [TBW1 路径] > bin > 调试 >TBW1.dll > 确定添加对带有水印项目的文本框的引用。

构建项目。该控件现在在工具箱中可用。双击得到测试中的一个控件window。它看起来应该与普通的 TextBox 控件完全一样。

单击控件并在属性 window 中检查其属性。两个新定义的属性 WatermarkColor 和 WatermarkText 将显示在属性列表的末尾附近。要测试功能,请提供不同的颜色(例如红色)和文本阅读(例如 "Type here".

有效!但是,有两个问题:

(1) 控件第一次显示时只工作一次。输入内容然后删除文本会使文本框为空。我想再看看水印文字

(2) 第一次显示水印文本时,会显示正确的字体(继承自表单)。开始打字的时候用的是难看的系统字体

如何解决这两个问题?

编辑

根据 VisualVincent 的评论,我改进了 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

并添加

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

    Me.Invalidate()
End Sub

出现水印。开始书写时,文本出现,但仍然是丑陋的系统字体。当我将鼠标悬停在它上面时,文本消失了。

当我删除文本时,水印不会重新出现,除非我将鼠标悬停在它上面。

编辑:由于 OP 放弃自定义前景色要求而复活。

此功能由 WinForm 文本框 class 包装的本机编辑控件支持。如果启用视觉样式,Windows Vista 及更高版本支持它。

参考:EM_SETCUEBANNER message

示例:

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

编辑: 我不建议使用此答案,请参阅评论。使用 TnTinMan 的回答。


OnPaint 应该正确地完成所有绘图。只是它不是出于我无法理解的原因。 (原来,TextBox不是一个.Net控件,实际上,它只是包装了一个Win32控件。)

但是,覆盖 WndProcWM_PAINT 消息 有效。

这是一个有效的 TextBox,添加了两个属性:WatermarkTextWatermarkColor

Public Class UTextBoxWatermark
    Inherits TextBox

    '============================================================================
    'CONSTANTS.
    '============================================================================
    Const WM_PAINT As Integer = &HF

    '============================================================================
    'VARIABLES.
    '============================================================================
    Private gsWatermarkText As String = "Watermark"
    Private glWatermarkColor As Color = Color.Gray

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

    Public Property WatermarkColor As Color
        Get
            Return glWatermarkColor
        End Get
        Set(lValue As Color)
            glWatermarkColor = lValue
            Me.Invalidate()
        End Set
    End Property

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

    '============================================================================
    'EVENT HANDLERS.
    '============================================================================
    Protected Overrides Sub WndProc(ByRef m As Message)
        MyBase.WndProc(m)

        Dim oBrush As SolidBrush

        If m.Msg = WM_PAINT Then
            'If the text is empty now, the watermark text should be written 
            'instead.
            If Me.Text.Length = 0 Then
                oBrush = New SolidBrush(glWatermarkColor)
                Using oGraphics As Graphics = Me.CreateGraphics
                    oGraphics.DrawString(gsWatermarkText, Me.Font, oBrush, 0, 0)
                End Using
            End If
        End If
    End Sub

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

        If Me.Text.Length = 0 Then
            Me.Invalidate()
        End If
    End Sub
End Class