创建自定义文本框以添加到工具箱并在表单中使用

Create a custom TextBox to add to the Toolbox and use in a Form

我是 Visual Studio 的新手,我想创建自己的文本框,它可以具有可自定义的边框(将来可能还有其他东西)。
现在,我的方法是将一些东西合并在一起,形成我自己创作的弗兰肯斯坦。

我已经创建了一个 Class、构建它、浏览并将其添加到我的表单(另一个项目)中的工具箱中,并将其放入表单中。当我构建并打开窗体时(没有错误),所有控件都显示在属性中并且 TextBox 加载,但当然,它只是显示为常规 TextBox(无彩色边框)。

我不明白为什么它不起作用。是我创建问题的矩形,还是某种绘画问题,与 Usercontrols 或其他我缺少的东西有关?

代码:

Imports System.Windows.Forms
Imports System.Drawing

Public Class CustomTextBox
    Inherits System.Windows.Forms.TextBox
    Public Enum BorderSideOptions
        Left
        Right
        Top
        Bottom
        All
    End Enum

    Dim BrdrColor As Color = Color.Blue
    Dim BrdrSize As Single = 1
    Dim BrdrStyle As ButtonBorderStyle = ButtonBorderStyle.Solid
    Dim BorderSide As BorderSideOptions = BorderSideOptions.All

    Public Sub New()
        Me.Width = 120
        Me.Height = 20
        Me.BackColor = Color.White
        Me.ForeColor = Color.Black
    End Sub


    Property BorderSides As BorderSideOptions
        Get
            Return BorderSide
        End Get
        Set(value As BorderSideOptions)
            BorderSide = value
        End Set
    End Property

    Property BorderColor() As Color
        Get
            Return BrdrColor
        End Get
        Set(value As Color)
            BrdrColor = value
        End Set
    End Property

    Property BorderSize() As Single
        Get
            Return BrdrSize
        End Get
        Set(value As Single)
            BrdrSize = value
        End Set
    End Property

    Overloads Property BorderStyle() As ButtonBorderStyle
        Get
            Return BrdrStyle
        End Get
        Set(value As ButtonBorderStyle)
            BrdrStyle = value
        End Set
    End Property

    Private Sub CustomTextBox_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
        Dim txtRect As Rectangle, B As Color = Color.Black
        txtRect = New Rectangle(Location, Size)

        Select Case BorderSides
            Case BorderSideOptions.All
                ControlPaint.DrawBorder(e.Graphics, txtRect, BrdrColor, BrdrSize, BrdrStyle, BrdrColor, BrdrSize, BrdrStyle, BrdrColor, BrdrSize, BrdrStyle, BrdrColor, BrdrSize, BrdrStyle)
            Case BorderSideOptions.Bottom
                ControlPaint.DrawBorder(e.Graphics, txtRect, B, 0, ButtonBorderStyle.None, B, 0, ButtonBorderStyle.None, B, 0, ButtonBorderStyle.None, BrdrColor, BrdrSize, BrdrStyle)
            Case BorderSideOptions.Left
                ControlPaint.DrawBorder(e.Graphics, txtRect, BrdrColor, BrdrSize, BrdrStyle, B, 0, ButtonBorderStyle.None, B, 0, ButtonBorderStyle.None, B, 0, ButtonBorderStyle.None)
            Case BorderSideOptions.Right
                ControlPaint.DrawBorder(e.Graphics, txtRect, B, 0, ButtonBorderStyle.None, B, 0, ButtonBorderStyle.None, BrdrColor, BrdrSize, BrdrStyle, B, 0, ButtonBorderStyle.None)
            Case BorderSideOptions.Top
                ControlPaint.DrawBorder(e.Graphics, txtRect, B, 0, ButtonBorderStyle.None, BrdrColor, BrdrSize, BrdrStyle, B, 0, ButtonBorderStyle.None, B, 0, ButtonBorderStyle.None)
        End Select
    End Sub
End Class

一些修改和一些提示:

  • 您有一个 Paint 事件处理程序,它来自 OP 的任何地方;这将不起作用,并且在任何情况下,您使用自定义控件覆盖引发事件的方法(OnPaint(),在这种情况下),您不订阅事件
  • Border 粗细非常有限,1 或 2 个像素:这是非客户区的大小(如果您需要更粗的边框,则需要一个承载 TextBox 并在粗细为时向外扩展的 UserControl > 2).
    属性 setter 需要考虑这个限制。在修改后的代码中有一个 Min/Max 检查。
  • 基数class的BorderSize只应设置为默认BorderStyle.Fixed3D。您可以设置其他样式,但是:1) 实际上没有理由 2) 您还需要处理 WM_PAINT 以在客户区内绘制(不推荐,并且如前所述,在这里并不完全有用)。
  • 每次更改影响图形的 属性 值时,您需要使控件无效,以便立即在设计器中和 运行 时应用效果。
    修改后的代码使用 Parent?.Invalidate(Bounds, True)(强制控件的父级(如果有的话)使该控件所在的客户区部分无效。True 参数指示使子级无效)。
    如果没有Parent,此时Custom Control很可能也没有Handle,所以实际上并没有绘制。
    这在两种情况下都有效:当任何受影响的属性在 PropertyGrid 或代码中发生更改时,新状态将应用并立即可见(如前所述,如果控件具有句柄)。
  • 具有默认值的自定义属性应使用 DefaultValue 属性进行修饰。默认的 属性 值没有在设计器中序列化(在 Designer.vb / Designer.cs 文件中)并且可以在自定义设计器、类型转换器和其他超出本文范围的东西中使用post
  • Option Strict 设置为 ON 总是一个好主意(当你这样做时检查你的代码)。

  • TextBox 控件不会引发 Paint 事件(没有 调整 它,反正在这里用处不大),所以覆盖 OnPaint() 什么都不做
  • 由于要在客户区外绘制边框,所以设置了陷阱WM_NCPAINT消息,当其非客户区需要重绘时发送给控件
  • 您可以通过处理 WM_PAINT.
    在客户区内绘制边框(或其他任何东西) 参见示例:TextBox with dotted lines for typing
  • 要处理这些消息,您必须覆盖控件的 WndProc(您可能已经在其他地方看到 LRESULT CALLBACK WindowProc(...)
  • 要在非客户区上绘制,您需要此控件的设备上下文。 WM_NCPAINT 将指针传递给 WParam 中的裁剪区域,但使用 GeWindowDC() function, then derive a Graphics object from the returned HDC using the managed Graphics.FromHdc() method. The HDC needs to be released calling ReleaseDC()
  • 更简单

如果你想创建一个可以在任何其他解决方案/项目中使用的自定义控件,构建一个 Class 库并将这个或其他控件添加到这个程序集中(小心选择命名空间)。
Project->Properties->Compile 中的目标 CPU 设置为 AnyCPU


Imports System.ComponentModel
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

<DesignerCategory("Code")>
Public Class CustomTextBox
    Inherits TextBox

    Private Const WM_NCPAINT As Integer = &H85
    Private m_BorderColor As Color = Color.Blue
    Private m_BorderSize As Integer = 1
    Private m_BorderStyle As ButtonBorderStyle = ButtonBorderStyle.Solid
    Private m_BorderSides As BorderSideOptions = BorderSideOptions.All

    Public Sub New()
    End Sub

    <DefaultValue(BorderSideOptions.All)>
    Public Property BorderSides As BorderSideOptions
        Get
            Return m_BorderSides
        End Get
        Set
            If m_BorderSides <> Value Then
                m_BorderSides = Value
                Parent?.Invalidate(Bounds, True)
            End If
        End Set
    End Property

    <DefaultValue(KnownColor.Blue)>
    Public Property BorderColor As Color
        Get
            Return m_BorderColor
        End Get
        Set
            If m_BorderColor <> Value Then
                m_BorderColor = Value
                Parent?.Invalidate(Bounds, True)
            End If
        End Set
    End Property

    <DefaultValue(1)>
    Public Property BorderSize As Integer
        Get
            Return m_BorderSize
        End Get
        Set
            Dim newValue = Math.Max(Math.Min(Value, 2), 1)
            If m_BorderSize <> newValue Then
                m_BorderSize = newValue
                Parent?.Invalidate(Bounds, True)
            End If
        End Set
    End Property

    <DefaultValue(ButtonBorderStyle.Solid)>
    Public Overloads Property BorderStyle As ButtonBorderStyle
        Get
            Return m_BorderStyle
        End Get
        Set
            If m_BorderStyle <> Value Then
                m_BorderStyle = Value
                Parent?.Invalidate(Bounds, True)
            End If
        End Set
    End Property

    Protected Overrides Sub OnHandleCreated(e As EventArgs)
        MyBase.OnHandleCreated(e)
        MyBase.BorderStyle = Windows.Forms.BorderStyle.Fixed3D
    End Sub

    Protected Overrides Sub WndProc(ByRef m As Message)
        MyBase.WndProc(m)
        Select Case m.Msg
            Case WM_NCPAINT
                If Not IsHandleCreated Then Return
                Dim rect = New Rectangle(0, 0, Width, Height)
                Dim hDC = GetWindowDC(Handle)
                Try
                    Using g = Graphics.FromHdc(hDC),
                       p As New Pen(BackColor, 2)
                        g.DrawRectangle(p, rect)
                        Select Case BorderSides
                            Case BorderSideOptions.All
                                ControlPaint.DrawBorder(g, rect, m_BorderColor, m_BorderSize, m_BorderStyle, m_BorderColor, m_BorderSize, m_BorderStyle, m_BorderColor, m_BorderSize, m_BorderStyle, m_BorderColor, m_BorderSize, m_BorderStyle)
                            Case BorderSideOptions.Bottom
                                ControlPaint.DrawBorder(g, rect, Nothing, 0, 0, Nothing, 0, 0, Nothing, 0, 0, m_BorderColor, m_BorderSize, m_BorderStyle)
                            Case BorderSideOptions.Left
                                ControlPaint.DrawBorder(g, rect, m_BorderColor, m_BorderSize, m_BorderStyle, Nothing, 0, 0, Nothing, 0, 0, Nothing, 0, 0)
                            Case BorderSideOptions.Right
                                ControlPaint.DrawBorder(g, rect, Nothing, 0, 0, Nothing, 0, 0, m_BorderColor, m_BorderSize, m_BorderStyle, Nothing, 0, 0)
                            Case BorderSideOptions.Top
                                ControlPaint.DrawBorder(g, rect, Nothing, 0, 0, m_BorderColor, m_BorderSize, m_BorderStyle, Nothing, 0, 0, Nothing, 0, 0)
                        End Select
                    End Using
                Finally
                    ReleaseDC(Handle, hDC)
                End Try
                m.Result = IntPtr.Zero
        End Select
    End Sub

    ' This could use a file of its own
    Public Enum BorderSideOptions
        Left
        Right
        Top
        Bottom
        All
    End Enum

    ' Native methods
    <DllImport("user32")>
    Private Shared Function GetWindowDC(hwnd As IntPtr) As IntPtr
    End Function

    <DllImport("user32")>
    Private Shared Function ReleaseDC(hwnd As IntPtr, hDc As IntPtr) As Integer
    End Function

End Class