Introduction
I've been looking around a bit to find an example of how to get a Windows Forms TextBox painted with rounded corners, but I did not find a solution. I collected some useful tips, put them all together with some logic, and came up with a satisfying solution. Because I have found many excellent coding tips on this site, I'm posting the source here so it hopefully can be useful for someone.
Overview
To come up with the rounded corners textbox, I started trying to work with the paint override event, but unfortunately without any result, which is due to the fact (I assume) that the textbox is derived from Windows. Therefore, I tried overriding the WM_PAINT
API instead, which had the desired results.
The tricky part then was that I first could only draw on the client area of the textbox, which resulted in a very small textbox. Once again, API came up with a solution: I added two declarations, one for GetWindowDC
to get a handle on the non-client area, and one for ReleaseDC
to clean up after my custom drawing.
Now that I could fully use the graphics of my textbox, and added the coding to draw a rounded rectangle, I was still not completely satisfied because the height of my textbox was dependent on the font size. I tried searching a way to override this "hidden" autosize event, but did not find a working solution. I came up with a way to be able to change the textbox's height by setting the multiline feature to true, but I still believe there has to be a better way for this. Because I also added a method which catches the Enter-keypress and which returns a Tab-keypress instead, the multiline solution will do for my needs.
Because the rounded textbox control is part of a bigger solution I recently wrote, you will also find some custom properties, OriText
, NewText
, and a custom event Texthaschanged
, which actually is a way to check whether the control text has changed before the Onleave
-event takes place, and the custom properties provide access to the original text as well as the new text. I needed these properties and left them in the control, maybe someone finds them useful.
The code
OK, time for the code.
The procedure below handles the event of pressing the Enter key, as well as the event of pressing the "," key. The Enter key has been replaced by a Tab key so the next control in the tab order will be selected. The second part of the procedure is to handle the input of a decimal character and format the input according to Belgian point for decimal format.
Protected Overrides Function ProcessCmdKey(ByRef msg _
As System.Windows.Forms.Message, _
ByVal keyData As System.Windows.Forms.Keys) As Boolean
If msg.WParam.ToInt32() = CInt(Keys.Enter) Then
SendKeys.Send("{Tab}")
Return True
ElseIf msg.WParam.ToInt32() = CInt(Keys.Decimal) Then
SendKeys.Send(",")
Return True
End If
End Function
The next method is the actual overriding of the WM_Paint
event, in which the redrawing is done. It makes uses of the API functions GetWindowDC
and ReleaseDC
to get the actual graphics of the control, including the non-client area.
Protected Overrides Sub WndProc(ByRef m As _
System.Windows.Forms.Message) Handles MyBase.WndProc(m)
Select Case m.Msg
Case &HF Dim rect As New Rectangle(0, 0, MyBase.Width, MyBase.Height)
Dim hDC As IntPtr = GetWindowDC(Me.Handle)
Dim g As Graphics = Graphics.FromHdc(hDC)
If Me.Enabled Then
g.Clear(Color.White)
Else
g.Clear(Color.FromName("control"))
End If
DrawBorder(g)
DrawText(g)
ReleaseDC(Me.Handle, hDC)
g.Dispose()
Case &H7, &H8, &H200, &H2A3
UpdateState()
End Select
End Sub
To get the rounded corners, the method shown below is used.
Private Sub TekenRondeRechthoek(ByVal g As Graphics, _
ByVal pen As Pen, ByVal rectangle As Rectangle, _
ByVal radius As Single)
Dim size As Single = (radius * 2.0!)
Dim gp As GraphicsPath = New GraphicsPath
gp.AddArc(rectangle.X, rectangle.Y, size, size, 180, 90)
gp.AddArc((rectangle.X + (rectangle.Width - size)), _
rectangle.Y, size, size, 270, 90)
gp.AddArc((rectangle.X + (rectangle.Width - size)), _
(rectangle.Y + (rectangle.Height - size)), _
size, size, 0, 90)
gp.AddArc(rectangle.X, (rectangle.Y + _
(rectangle.Height - size)), size, size, 90, 90)
gp.CloseFigure()
g.DrawPath(pen, gp)
gp.Dispose()
End Sub
The complete code example is included in the attached zip-file. I hope it can be useful for anyone.