Reputation: 1375
I'm very new to Visual Studio and I'm wanting to create my own TextBox that can have customizable borders (and maybe other stuff in future).
Right now, my method is merging a couple things together to sort of Frankenstein my own creation.
I've created a Class, built it, browsed and added it to the Toolbox in my Form (another project) and placed it in the Form. All the controls show up in the properties and the TextBox loads when I build and open the Form (no errors) but of course, it just shows up as a regular TextBox (no colored border).
I can't figure out why it's not working. Is it the rectangle that I create the problem, or some sort of painting problem, something to do with Usercontrols or something else that I'm missing?
Code:
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
Upvotes: 1
Views: 1066
Reputation: 32278
Some modifications and a few pointers:
Paint
event handler that comes from nowhere in the OP; that won't work and, in any case, with a Custom Control you override the methods that raise the events (OnPaint()
, in this case), you don't subscribe to the eventsBorderSize
of the base class should be set to the default BorderStyle.Fixed3D
only. You can set other styles, but: 1) there's actually no reason 2) you'd need to also handle WM_PAINT
to draw inside the client area (not recommended and, as mentioned, not exactly useful here).Parent?.Invalidate(Bounds, True)
(forces the Parent of the Control, if any, to invalidate the section of its Client Area where this Control is positioned. The True
argument instructs to invalidate the children).Designer.vb
/ Designer.cs
file) and can be used in custom Designers, Type converters and other stuff that are beyond the scope of this postOption Strict
to ON
is always a good idea (check your code when you do).OnPaint()
does nothingLRESULT CALLBACK WindowProc(...)
somewhere else)WM_NCPAINT
passes a pointer to the clipping region in WParam
, but it's simpler to use the 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()If you want to create a Custom Control that can be used in any other Solution / Project, build a Class Library and add this or other Controls to this assembly (pick the namespaces with care).
Set the Target CPU in Project->Properties->Compile
to 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
Upvotes: 1