Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

advanced TextBox

0.00/5 (No votes)
7 May 2014 1  
Inherited Textbox with extended properties and validation control.

Introduction

I know that in the meantime there will be several enhanced texbox controls available in the Internet. But developing my applications I need some functionality that is only available spreaded over several custom controls and not collected into one control. Therefor this control was developed. May be it will fit also your needs.

Background

This control has the functionality as followes:

  • Mark, put a small colored triangle in one of the controls edge
  • Mark Position, set the edge position
  • Context Menu, validation controlled clipboard action cut, copy, paste, delete
  • Input Type, No Validation, validate input: Integer or Decimal

    If validated:
  • Decimal Places, number of decimal digits allowed
  • Validate/set input value range Max/Min
  • Allow/refuse negativ values
  • Beep, if validation error send system beep tone
  • Flash, if validation error flash backcolor 3 times
  • Menue Item, Textstring of context menu item

Notice:

  • The validation of the input is only done for values by key stroke or pasted values from the clipbord. It is not validated if the value comes out of the binding to a datasource column.
  • If you set the Input type to Decimal and the decimal places are zero then the input is the same as for setting it to Integer.
  • If you paste a value from the clipboard and the Input type is set to Integer or Decimal. Pasted text is filled in as the correct number value. In case of pasted decimal value all decimal digits are rounded to the correct digit places. In case of pasted decimal value and Integer validation is set the decimal digits are rounded to an integer value.
  • The source code projects are checked for threads by a malware and antivirus software. Not threads are reported.

Using the code

The 1st ZIP File contains a project with two sub projects. The controls project and a windows forms test project. The projects are developed with VS2012 .NET 4.5 in VB.NET. The 2nd one includes only the controls DLL FIle.

To use the Project unzip the file and start it with Visual Studio (VS).
To use the control save the DLL in the controls project bin/Debug folder to the place you want and implement it in the VS Toolbox as usual. Drag the control from the Toolbox onto the windows form in designer mode.

The controls project contains three classes. One for the control itself (main class clsAdvTexbox.vb) and one for the used context menu (clsContextMenu.vb) and the third one is a helper class (clsToolBoxIcon.vb).

We need an own context menu to have the possibility to validate wether the pasted text from the clipboard is valid.

To have all properties of a standard textbox control available in the property grid the control is not build from a usercontrol. Instead of this it is build by inheritance.

Public Class advancedTexbox
    Inherits TextBox

All custom properties are well documented like this (think of not to forget imports System.ComponentModel):

 <Browsable(True), DisplayName("Triangle Position"), _
 Description("Choose Triangle Position"), _
 Category("custom Properties")> _
 Public Property TriPosition As _TriPos
.... 

Browsable, show/hide the property in the property grid.
DisplayName, Show this text in the property grid to identify the property.
Description, Show this text in the property grid footer.
Category, Group this property if the property grids grouping is activated.

The Visual Studio ToolBox Bug (clsTexBoxIcon)

Up from the fist version of Visual Studio the development system has problems to display toolbox icons of custom controls correct inside the toolbox. If you look into the VS Help you will be told to enhance the class statement like this:

<Toolboxitem("name.bmp"> _
class  form1

But in most cases this will not work. But there is a work around by adding the class "clsToolBoxIcon.vb" to your Project.

<Serializable()> Friend Class TheToolboxItem
    Inherits ToolboxItem 
    ' ## change the parameter of the GetType Statement to the name of the usercontrol class 
    Public Sub New(ByVal oType As Type) 
         MyBase.New(oType) 
    End Sub 
    Public Overrides Sub Initialize(ByVal oType As Type) 
        If Not oType.Equals(GetType(advancedTexbox)) Then
              String.Format(CultureInfo.CurrentCulture, _
               "constructor {0} must be {1}.", 
                Me.GetType().FullName, GetType(advancedTexbox).FullName))
        End If
        MyBase.Initialize(oType)
    End Sub
End Class   

Use this class as it is. The only thing to do is to change the references to the class of the custom control. To do this fill in the GetType Statement the name of the class (see bold text above).

Next you have to change your custom control class statement as followes:

<ToolboxItem(True)> <ToolboxBitmap(GetType(TheToolboxItem), _
"numTextbox.bmp")> Public Class advancedTexbox
    Inherits TextBox

Do not forget to create the icon in bmp format by 16x16 pixel and to place the file in the project.

clsContextMenu

This class creates a context menu with the menu items cut, copy, paste and delete. There is nothing special so it is not described here. The context menue is a nested control of the main custom TextBox control. Therefor we need some custom properties "Menueitem Cut" a.s.o. to lay out the menue items text property.

clsAdvTextBox

The code is well documented and in most cases intuitive readable also for beginners. The parts of the source code are segmented by regions for better readability. The heavy code parts are described below.

Draw a mark (triangle) ontop of the control in one of the controls edge.

Sometimes it might be useful to mark the TextBox to show the user that this is a special TextBox. For example that the TextBox input is controlled or it's input is mandatory. To draw the mark (colored triangle, see picture above) we have to capture a windows event (WndProc) because the standard TextBox does not have a paint event.

    ' ## draw colored triangle i a corner
    ' ## if pen color is transparent don't draw
    Protected Overrides Sub WndProc(ByRef m As Message)
        MyBase.WndProc(m)
        If m.Msg = &HF And Not Triangle Is Pens.Transparent Then ' WM_PAINT
            ' ## define triangle position
            Dim x2, y2 As Integer
            Select Case TriPosition
                Case _TriPos.Bottom_Left
                    x2 = 6 : y2 = Me.Height - 6
                Case _TriPos.Bottom_Right
                    x2 = Me.Width - 6 : y2 = Me.Height - 6
                Case _TriPos.Top_Left
                    x2 = 6 : y2 = 6
                Case _TriPos.Top_Right
                    x2 = Me.Width - 6 : y2 = 6
            End Select

            ' ## define triangle shape
            Dim points(3) As Point
            points(0) = New Point(x2 - 6, y2)
            points(1) = New Point(x2, y2 - 4)
            points(2) = New Point(x2, y2)
            points(3) = New Point(x2 - 6, y2)
            
            ' ## draw shape
            Dim g As Graphics = Graphics.FromHwnd(m.HWnd)
            g.DrawPolygon(Triangle, points)
            g.Dispose()
        End If
    End Sub

The color of the traingle can be choosen by the custom property TriangleColor, the position can be choosen by the custom property TriPosition. If the choosen color is transparent no triangle is drawn. To keep all elements of the control (text and triangle) tidy we have to call the me.refresh method to force the control to redraw if the controls text changes. We do that in the controls Me.TextChanged event.

Validate the input

To control the input we use the controls Me.KeyPress event:

 ' ##### Control the input value
    Private Sub numericTexbox_KeyPress(sender As Object, e As KeyPressEventArgs) Handles Me.KeyPress

        ' ## check if validation is required
        If InputType = _IType.NoValidation Then Return

        ' ## check if key pressed is backspace or delete, avoid group seperator
        If e.KeyChar = CChar(ChrW(Keys.Back)) Or e.KeyChar = CChar(ChrW(Keys.Delete)) And e.KeyChar <> GroupeSeparator Then
            Return
        End If

        ' ## check Range
        If Range Then
            If Char.IsDigit(e.KeyChar) Then
                Dim strTemp As String = Me.Text + e.KeyChar
                If Val(strTemp) >= RangeMin And Val(strTemp) <= RangeMax Then Return
            End If
        Else
            ' ## check for decimal
            If _InputType = 2 And _decimals > 0 And Char.IsDigit(e.KeyChar) Then
                ' ## check decimal places
                Dim temp() = Me.Text.Split(DecSeparator)
                If temp.Length = 2 Then

                    If temp(1).Length <= _decimals - 1 Then Return
                ElseIf temp.Length = 1 Then
                    Return
                End If
            ElseIf _InputType = 2 And e.KeyChar = DecSeparator AndAlso (Not Me.Text.Contains(DecSeparator)) Then
                Return
            ElseIf _InputType = 2 And _decimals = 0 Then
                _InputType = 1
            End If

            ' ## check integer
            If _InputType = 1 And Char.IsDigit(e.KeyChar) Then
                DecimalPlaces = 0
                Return
            End If

            ' ## check if negativ is allowed and negative is pressed as 1st char (leading negativ sign)
            If Me.Text.Length = 0 And e.KeyChar = "-" And _negativ And (Not Me.Text.Contains(DecSeparator)) Then Return
        End If

        ' ## all other cases are not valid
        e.Handled = True ' abord key press
        If Flash Then timerFlash.Enabled = e.Handled
        If e.Handled And BP Then Beep()
    End Sub

Validate clipboard actions

To control the clipboard actions we have to strip off the standard texbox context menu. This is done by setting the controls property me.shortcutsEnabled to false. We do this in the controls New() event. The definition of the custom context menu is outsourced in the separate class clsContextMenu for more transparency. If a Menuitem is clicked the custom event CM_MenueItem of the class clsContextMenu is fired to transfer the clicked item information into the controls main class clsAdvTextbox.

class clsContextMenu:

Public Class cMenue
    Inherits ContextMenuStrip

    Friend WithEvents mnCut As System.Windows.Forms.ToolStripMenuItem
    Friend WithEvents mnCopy As System.Windows.Forms.ToolStripMenuItem
    Friend WithEvents mnPaste As System.Windows.Forms.ToolStripMenuItem
    Friend WithEvents ToolStripSeparator1 As System.Windows.Forms.ToolStripSeparator
    Friend WithEvents mnDel As System.Windows.Forms.ToolStripMenuItem

    Public Event MenueItemClicked(ByVal sender)
... 
   Private Sub mnCopy_Click(sender As Object, e As EventArgs) Handles mnCopy.Click
        RaiseEvent MenueItemClicked(sender)
    End Sub

    Private Sub mnCut_Click(sender As Object, e As EventArgs) Handles mnCut.Click
        RaiseEvent MenueItemClicked(sender)
    End Sub

    Private Sub mnDel_Click(sender As Object, e As EventArgs) Handles mnDel.Click
        RaiseEvent MenueItemClicked(sender)
    End Sub

    Private Sub mnPaste_Click(sender As Object, e As EventArgs) Handles mnPaste.Click
        RaiseEvent MenueItemClicked(sender)
 End Sub 

class clsAdvTextBox:

    ' ##### control clipboard actions"
    Private Sub CM_MenueItemClicked(sender As Object) Handles CM.MenueItemClicked
        Select Case sender.name
            Case "mnCut"
                Clipboard.SetText(Me.SelectedText)
                Me.SelectedText = ""
            Case "mnCopy"
                If Me.SelectedText = "" Then
                    Clipboard.SetText(Me.Text)
                Else
                    Clipboard.SetText(Me.SelectedText)
                End If
            Case "mnPaste"
                If InputType <> _IType.NoValidation Then
                    If IsNumeric(Clipboard.GetText) Then
                        If InputType = _IType.Integer Then
                            Me.Text = CInt(Clipboard.GetText)
                        ElseIf InputType = _IType.Decimal Then
                            If DecimalPlaces = 0 Then
                                Me.Text = CInt(Clipboard.GetText)
                            Else
                                Me.Text = Math.Round(CDbl(Clipboard.GetText), DecimalPlaces)
                            End If
                        End If
                    Else
                        If Flash Then timerFlash.Enabled = True
                        If BP Then Beep()
                    End If
                Else
                    If Me.SelectedText = "" Then
                        Me.SelectedText = Clipboard.GetText()
                    Else
                        Me.Text = Clipboard.GetText
                    End If
                End If
            Case "mnDel"
                Me.SelectedText = ""
        End Select
    End Sub
By the way, to avoid flickering of the triangle mark or the backcolor flashing it is recomended to set the parent forms property DoubleBufferd to true!

History

May 7th 2014 -11:15

  • Corrected some missing comments and text errors.
  • recompiled and rebuild ZIP files.
  • Added description for the Toolbox icon workaround
  • Added Toolbox icon workaround class
  • Corrections of localization, added properties for text of context menue items.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here