Completando el control MaskedTextBox con ceros a la izquierda. Al editar el control (y II)

En la primera entrega  de este artículo mostrábamos una sencilla técnica en el uso del control MaskedTextBox, consistente en completar un número con ceros a la izquierda. En esta segunda parte vamos a complicar la estrategia en la introducción del valor en la caja de texto, ya que será en el momento en el que el usuario vaya escribiendo cuando tendremos que ir añadiendo dinámicamente los ceros a la izquierda del número, dotándole de un comportamiento similar en algunos aspectos al de la calculadora.


La solución que proponemos mediante esta técnica pasa por la manipulación de los eventos de teclado que recibe el control: KeyDown, KeyPress y KeyUp; donde será nuestro código el encargado de calcular la longitud en dígitos del número introducido, y añadir ceros a la izquierda hasta completar la longitud de la máscara de entrada.


En primer lugar, al igual que hicimos en la entrega anterior, durante la carga del formulario estableceremos en el control MaskedTextBox la máscara de entrada para que acepte valores numéricos, mientras que el carácter marcador de dicha entrada será el cero.


private void frmDuranteEdicion_Load(object sender, EventArgs e)
{
this.maskedTextBox1.Mask = “99999”;
this.maskedTextBox1.PromptChar = ‘0’;
}

Seguidamente escribiremos en el manipulador del evento Enter del control -desencadenado cuando este obtiene el foco- el código necesario para situar el cursor de escritura al final del texto, ya que esto facilitará la introducción y el formateo dinámico del número a la hora de añadirle los ceros a la izquierda.


private void maskedTextBox1_Enter(object sender, EventArgs e)
{
SendKeys.Send(“{END}”);
}

A continuación pasaremos al primero de los eventos de teclado que se producen: KeyDown. Aquí debemos inhabilitar las pulsaciones que puedan interferir en la correcta introducción de los valores en el MaskedTextBox. Dichas teclas corresponderían a Inicio, Izquierda, Arriba y Abajo; y para deshabilitarlas comprobaremos el valor de la propiedad KeyEventArgs.KeyCode que recibe este evento, el cual contiene un elemento de la enumeración Keys. En caso de que haya coincidencia con alguna de las teclas mencionadas, asignaremos true a la propiedad KeyEventArgs.SupressKeyPress, que como su nombre indica, permite suprimir la pulsación de tecla efectuada, evitando que llegue al controlador de teclado.


Cuando manipulamos una pulsación de teclado para personalizar algún tipo de comportamiento en la edición de una caja de texto, tal y como hemos hecho ahora, habitualmente debemos asignar el valor true a la propiedad KeyEventArgs.Handled, para que el entorno de ejecución sepa que hemos alterado el funcionamiento normal de este evento. Sin embargo, en este caso en particular no es necesario, ya que la asignación que hemos realizado a la propiedad KeyEventArgs.SupressKeyPress se encarga de establecer el valor correcto en Handled.


private void maskedTextBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Left | e.KeyCode == Keys.Home |
e.KeyCode == Keys.Up | e.KeyCode == Keys.Down )
{
e.SuppressKeyPress = true;
}
}

Nuestro siguiente paso nos lleva al evento KeyPress, el cual nos permite saber, a través de su parámetro KeyPressEventArgs, el carácter correspondiente a la tecla pulsada. La mecánica seguida en este evento -que podemos ver esquemáticamente en la próxima figura-será la siguiente: puesto que estamos introduciendo los caracteres tecleados desde el final de la caja de texto, necesitamos comprobar si a la izquierda del número todavía hay ceros; en caso afirmativo, podemos añadir un nuevo valor al control, pero antes debemos comprobar que la tecla pulsada corresponde a un número, e insertarlo al final del cuadro de texto, desplazando el resto de valores y eliminando el primer cero a la izquierda existente.



La operación más inmediata que posiblemente pensaríamos llevar a cabo sería obtener el contenido de la propiedad Text del MaskedTextBox, para comprobar el valor del primer elemento de la cadena. Sin embargo, debemos tener en cuenta que la configuración de máscara -propiedad TextMaskFormat con el valor IncludeLiterals-que estamos usando con el control, no incluye los caracteres de marcador de entrada de valores, por lo que los ceros que son visualizados a la izquierda del número que introduzcamos, no formaran realmente parte del texto contenido en el control.


Para evitar este problema vamos a hacer uso de un objeto MaskedTextProvider, que obtenemos de la propiedad del mismo nombre existente en el control. La clase MaskedTextProvider proporciona un conjunto de miembros que nos van a permitir manejar con mayor flexibilidad el valor contenido en la caja de texto.


Por ejemplo, el método MaskedTextProvider.ToDisplayString sí que devuelve el texto al completo que visualizamos en el control, por lo que se convierte en el candidato perfecto para comprobar si el primer elemento es un cero; en caso afirmativo, usaremos el método MaskedTextProvider.VerifyChar para verificar si el carácter que pretendemos insertar en la última posición del control cumple con los requisitos de la máscara de entrada, en nuestro caso que sea un dato numérico.


VerifyChar devuelve un tipo bool, que nos informa si el carácter es válido para ser insertado en la posición indicada, pero a veces eso no resulta suficiente, y por ese motivo, el último parámetro -pasado por referencia- es un tipo enumerado MaskedTextResultHint, que contendrá información adicional acerca del resultado de este método.


Si el valor introducido es adecuado, volcaremos el texto del control en una colección Queue<char>, que representa una lista de tipo FIFO, para poder, de un modo más cómodo, quitar el primer elemento y añadir al final el que acaba de ser introducido por el usuario; volviendo a traspasar la colección al control.


private void maskedTextBox1_KeyPress(object sender, KeyPressEventArgs e)
{
MaskedTextProvider mtpMskTxtPrv = this.maskedTextBox1.MaskedTextProvider;

if (mtpMskTxtPrv.ToDisplayString().Substring(0, 1) == mtpMskTxtPrv.PromptChar.ToString())
{
MaskedTextResultHint mtrhResultado;

if (mtpMskTxtPrv.VerifyChar(e.KeyChar, mtpMskTxtPrv.Mask.Length – 1, out mtrhResultado))
{
Queue<char> queNumero = new Queue<char>(mtpMskTxtPrv.ToDisplayString().ToCharArray());
queNumero.Dequeue();
queNumero.Enqueue(e.KeyChar);

this.maskedTextBox1.Text = new string(queNumero.ToArray());
}
}
}


Para concluir con el proceso debemos controlar, en el evento KeyUp del MaskedTextBox, las pulsaciones de las teclas Suprimir y Retroceso, de forma que eliminen el último dígito existente en la caja de texto del control.


Volveremos, en esta ocasión, a utilizar el objeto MaskedTextProvider, llamando a su método ToDisplayString para obtener la cadena contenida en el control, de la que eliminaremos el último carácter y rellenaremos con espacios a la izquierda, todo ello en la misma línea de código. La forma de obtener la posición a borrar y la longitud que debe tener la cadena resultante la averiguaremos mediante la propiedad Mask.Length del control.


Por otro lado, si borramos todos los números y nos quedamos solamente con los ceros marcadores de entrada, lo que sucederá es que la caja de texto estará realmente vacía. Para obligar a que el cursor de escritura se mantenga al final del control aunque pulsemos la tecla Retroceso, enviaremos al controlador de teclado una pulsación de la tecla Fin mediante la clase SendKeys.


private void maskedTextBox1_KeyUp(object sender, KeyEventArgs e)
{
MaskedTextProvider mtpMskTxtPrv = this.maskedTextBox1.MaskedTextProvider;

if (e.KeyCode == Keys.Delete | e.KeyCode == Keys.Back)
{
this.maskedTextBox1.Text = mtpMskTxtPrv.ToDisplayString().Remove(
mtpMskTxtPrv.Mask.Length – 1, 1).PadLeft(mtpMskTxtPrv.Mask.Length);

if (this.maskedTextBox1.Text.Length == 0)
{
SendKeys.Send(“{END}”);
}
}

this.lblNumero.Text = this.maskedTextBox1.MaskedTextProvider.ToDisplayString();
}


En todo momento visualizaremos el valor completo introducido mediante un control Label, al que asignaremos el resultado del método MaskedTextProvider.ToDisplayString.


La siguiente figura muestra el control en ejecución.



Y con esto concluimos la segunda entrega de este artículo dedicado a la manipulación del control MaskedTextBox, de la que también tenemos el código fuente disponible en los siguientes enlaces  C# y VB.


Espero que os resulte de utilidad.


Un saludo.

8 Comentarios

  1. anonymous

    Luis:
    muy claro y prolijo su artículo, le agradezco que comparta su saber haciéndonos más fácil el aprendizaje a los que estamos en ese camino. Llequé a este artículo buscando la manera de personalizar un textbox, o un maskedtextbox, para recibir la entrada de números decimales. Tengo la necesidad de lograr el siguiente comportamiento: la caja debe mostrar al principio “0,00”; y debe permitir el ingreso de hasta 2 dígitos enteros y 2 decimales. La manera de ingresar los decimales debe ser luego de haber presionado 2 dígitos o un dígito y el punto, o ningún dígito y el punto. El punto debe “dibujarse” como una coma, y no tiene que poder ser borrado. Es decir al presionar el punto el cursor debe “saltar” detrás de la coma. Tiendo a pensar que para lograr esto debo hacerlo por código, quizás heredando una nueva clase personalizada a partir del textbox. Mi pregunta es si éste es el camino o puede lograrse lo requerido mediante el seteo de propiedades del textbox o del maskedtextbox.
    Bueno, espero haberme explicado bien, muchas gracias de nuevo y gracias por su atención.
    Saludos, Mauro.
    maurofranzoy@hotmail.com

  2. lmblanco

    Hola Mauro

    Gracias por leer el artículo, me alegra que te haya parecido interesante 😎

    Respecto a la duda que me planteas, yo creo que podrías resolverla sin necesidad de crear una clase derivada del control MaskedTextBox. Según los requerimientos que me comentas, lo que puedes desarrollar es un proceso que al igual que en el artículo, utilice los diferentes eventos de pulsación de teclado disponibles por el control KeyDown, KeyPress, etc., de forma que vayas detectando las pulsaciones de tecla.

    La máscara que deberías utilizar sería la siguiente: 90.00

    Mediante esa máscara, la primera posición de la parte entera es opcional, siendo las demás obligatorias numéricas. El punto de la máscara se visualizará como una coma.

    En cuanto al código, el proceso que podrías desarrollar consistiría en detectar si el usuario ha introducido un primer número en la parte entera, y si a continuación, en vez de introducir un segundo número, teclea un punto, reposicionas el número de la parte entera justo al lado de la coma y dejas el cursor de escritura a continuación de la coma.

    Espero que consigas solucionarlo.

    Un saludo.
    Luismi

  3. anonymous

    Hola Luis
    Sí, es como UD dice. Se puede hacer tal cuál lo describe. Lo único tendría que ver el tema de cómo hacer para que en la parte entera un 1 se vea como “1”, en vez de “01”, etc. Por otro lado le comento que desarrollé una clase que hereda de textbox con el comportamiento deseado. Me quedó bastante “grande”, unas 750 líneas jeje. Quizás por mi apuro por desarrollar y el tiempo que hace que uso VB.NET (mes y medio).

    Gracias por su comentario.
    Un saludo.
    Mauro.

    PD: ahora estoy con un campo que es para ingresar una fecha, creo que lo voy a encarar con el masked (porque tengo requrimientos “particulares”, como el caso anterior jeje)

  4. lmblanco

    Hola Mauro

    Quizá podrías establecer dinámicamente la máscara para el control en función del segundo valor tecleado por el usuario. Puedes partir con una máscara como comenté hace unos días: 90.00, y detectar qué valor se pulsa en la segunda posición: si es un dígito dejas la máscara actual, pero si es un punto, cambias la máscara a 9.00.

    Y mucha suerte también con el otro proceso para introducir fechas 8-).

    Un saludo.
    Luismi

  5. anonymous

    Hola Luis, muy pero muy util tu pagina y tus tutoriales, mira tengo una duda que no eh podido resolver, quiero tener una mascara de este tipo 00-0000000, que al escribir por ejemplo 1-86 , el resto se llene con 0,que quede asi, 01-0000086. tiene idea de como hacerlo, te agradesco.

  6. lmblanco

    Hola Alejandro

    Gracias por tu opinión. Respecto a la duda que me comentas, dado que se trata de un comportamiento especial, una posibilidad para resolverlo consistiría en mostrar inicialmente la máscara de entrada al usuario. Cuando el usuario pase el foco al control mediante teclado o ratón, limpias la máscara y le permites teclear el valor, y al salir del foco, formateas el valor que el usuario haya tecleado, redistribuyendo dicho valor hasta que se muestre con la máscara que necesitas.

    Un saludo.
    Luismi

  7. anonymous

    hola en el evento KeyDown quiero evitar que la tecla “delete” tenga funcionalidad para el control MaskedTextBox, utilizo la instrucción e.SuppressKeyPress = true
    pero a pesar de eso se borra un elemento del textbox, quisiera saber si no tienen el mismo problema o como lo puedo solucionar…

    ya que quiero que en el control aparezca una fecha en el siguiente formato __/__/2009, y cuando se presione la tecla DELETE se borrè el contenido y nuevamente se muestre como __/__/2009, actualmente al presionar la tecla la información se borra pero se ve el campo como __/_2/009…

    espero me puedan ayudar este es mi código, no se realmente cual es el problema porque para los controles e.SuppressKeyPress = True me funciona bien.

    Dim año As String=”2009″
    Private Sub Mtxt_Fecha_KeyUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Mtxt_FinPeriodo.KeyUp
    If e.KeyCode = Keys.Delete Then
    Mtxt_Fecha.Text = ” / /” & año
    e.Handled = True
    e.SuppressKeyPress = True
    End If
    End Sub

  8. lmblanco

    Hola MElena

    Puedes probar algo parecido al siguiente código, donde al obtener el foco el control, posicionamos el cursor al comienzo del mismo, y cuando pulsamos la tecla de borrado, borramos el contenido, dejando solamente el año. Con algunos retoques supongo que lo podrás adaptar al comportamiento que necesitas.

    //——————————-
    private void Form1_Load(object sender, EventArgs e)
    {
    this.maskedTextBox1.Text = ” / /2009″;
    }

    private void maskedTextBox1_Enter(object sender, EventArgs e)
    {
    SendKeys.Send(“{HOME}”);
    }

    private void maskedTextBox1_KeyUp(object sender, KeyEventArgs e)
    {
    if (e.KeyCode == Keys.Back)
    {
    this.maskedTextBox1.Text = ” / /2009″;
    e.Handled = true;
    }
    }
    //——————————-

    Un saludo.
    Luismi

Leave a Reply

Tema creado por Anders Norén