jueves, 2 de abril de 2020

Winforms TextBox personalizado

A todos los que nos dedicamos a esto de la programación nos ha pasado en alguna o muchas ocasiones que necesitemos de un determinado control ofrecido por el framework algo adicional. A veces, son pequeños detalles que nos facilitarían la tarea a realizar y otras veces son simplemente efectos visuales que consideramos atractivos. Sea cual sea la situación, voy a mostrar un ejemplo de como personalizar el control TextBox en .NET WinForms.
En este ejemplo se optimiza el control TextBox para actuar comprobando la fortaleza de las contraseñas introducidas en el mismo. Para ello se añaden varias propiedades: Validate (permite establecer si queremos o no esta funcionalidad adicional), MinChars (en la versión inicial permite definir el número mínimo de caracteres para considerar una contraseña mínimamente fuerte), Valid (indica la validez o no de la contraseña introducida en el control), Fortaleza (devuelve un valor indicativo de la fortaleza de la contraseña con respecto a los criterios establecidos).
En la primera versión del control (se encuentra comentada en el código) se utilizaba la propiedad MinChars y cuando la longitud de la cadena introducida es mayor que MinChars + 3 y cumple el patrón, esta es considera válida y el fondo del control se pone en color verde. Para valores menores no válidos el color de fondo va variando desde el rojo hasta el verde correspondiente a una contraseña fuerte y por tanto válida.
En la segunda versión, se utiliza una función que nos da un número indicativo de la fortaleza de la cadena de caracteres que se le pasa como parámetro para su uso como contraseña.
La función que calcula el nivel de dificultad de adivinar una contraseña comprueba primero la longitud de la misma y después la pondera comprobando también los distintos tipos de caracteres empleados en la misma.



Public Class EBSTextBox
    Inherits TextBox
    Private _validate As Boolean
    Private patron As String = "^[a-zA-Z0-9.,?_-]+$"
    <Description("Define si se realiza validación o no")>
    <Category("Seguridad")>
    <DefaultValue(False)>
    <Browsable(True)>
    Public Property Validate() As Boolean
        Get
            Return _validate
        End Get
        Set(ByVal value As Boolean)
            _validate = value
        End Set
    End Property
    Private _minChars As Integer
    <Description("Define el mínimo número de caracteres para una contraseña")>
    <Category("Seguridad")>
    <Browsable(True)>
    Public Property MinChars() As Integer
        Get
            Return _minChars
        End Get
        Set(ByVal value As Integer)
            _minChars = value
        End Set
    End Property
    Private _valid As Boolean
    Public ReadOnly Property Valid() As Boolean
        Get
            Return _valid
        End Get
    End Property
    Private _fortaleza As Integer
    Public ReadOnly Property Fortaleza() As Integer
        Get
            Return _fortaleza
        End Get
    End Property
    Protected Overrides Sub OnTextChanged(ByVal e As EventArgs)
        _valid = False
        If _validate = True Then
            ''Validaciones básicas
            'If Me.Text.Length < MinChars Then
            '    Me.BackColor = Color.Red
            'ElseIf Me.Text.Length < MinChars + 3 Then
            '    Me.BackColor = Color.OrangeRed
            'Else
            '    'Comprobamos si se cumplen los requisitos de complejidad
            '    If Not System.Text.RegularExpressions.Regex.IsMatch(Me.Text, patron) Then
            '        Me.BackColor = Color.Yellow
            '        _valid = False
            '    Else
            '        Me.BackColor = Color.Green
            '        _valid = True
            '    End If
            '    Me.BackColor = Color.Green
            '    _valid = True
            ''Validaciones mediante función ObtenerFortalezaClave
            _fortaleza = ObtenerFortalezaClave(Me.Text)
            If _fortaleza <= 4 Then
                _valid = False
                Me.BackColor = Color.Red
            ElseIf _fortaleza < 10 Then
                _valid = False
                Me.BackColor = Color.Orange
            ElseIf _fortaleza < 20 Then
                _valid = True
                Me.BackColor = Color.LightGreen
            Else
                _valid = True
                Me.BackColor = Color.Green
            End If
        End If
        MyBase.OnTextChanged(e)
    End Sub
    Private Function ObtenerFortalezaClave(ByVal clave As String)
        Dim fortaleza As Integer = 0
        If clave.Length < 4 Then
            fortaleza = 0
        ElseIf clave.Length < 6 Then
            fortaleza = 2
        ElseIf clave.Length < 10 Then
            fortaleza = 5
        Else
            fortaleza = 10
        End If
        If Not System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-z]+$") Then
            If System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-zñ0-9]+$") Then
                fortaleza = fortaleza * 2
            ElseIf System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-zñ0-9A-ZÑ]+$") Then
                fortaleza = fortaleza * 4
            ElseIf System.Text.RegularExpressions.Regex.IsMatch(clave, "^[a-zñA-ZÑ0-9\W]+$") Then
                fortaleza = fortaleza * 5
            End If
        End If
        Return fortaleza
    End Function
End Class


En la imagen siguiente se puede observar el efecto de los cambios introducidos en el control TextBox. Como se puede ver en el ejemplo de uso del control, se utiliza la propiedad Valid para decidir si se habilita/deshabilita el botón que lo acompaña.


En este caso, la función de comprobación de la fortaleza da más importancia al uso de mayúsculas o signos de puntuación que a la longitud de la cadena (sobre todo si esta es pequeña). Este es un criterio más o menos acertado que se puede adaptar a las necesidades en cada momento. Lo importante del ejemplo son las posibilidades de personalización que ofrecen los controles en .NET.

El código del formulario de prueba del control es el siguiente:

Public Class frmTestInheritedControl
    Private Sub EbsTextBox1_TextChanged(sender As Object, e As EventArgs) Handles EbsTextBox1.TextChanged
        If CType(sender, EBSTextBox).Valid Then
            btnEntrar.Enabled = True
        Else
            btnEntrar.Enabled = False
        End If
        lblFortaleza.Text = CType(sender, EBSTextBox).Fortaleza.ToString()
    End Sub
    Private Sub btnEntrar_Click(sender As Object, e As EventArgs) Handles btnEntrar.Click
        If EbsTextBox1.Valid Then
            lblPassword.Text = EbsTextBox1.Text
        Else
            lblPassword.Text = ""
        End If
    End Sub
End Class