创建自定义文本框以添加到工具箱并在表单中使用
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
我是 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 returnedHDC
using the managed Graphics.FromHdc() method. TheHDC
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